UI fixes and updates for dashboards and data source CRUD v2 (#2939)

* fixes for beta 8

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* frontend fixes

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* fixes

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* fixes and updates

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* updates and fixes

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* fixes

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* fixed deep scan issues

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* 0px to 0 and sock shop icon update

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* minor fix after review

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>

* fixes to translation and condition

Signed-off-by: ishangupta-ds <ishan@chaosnative.com>
This commit is contained in:
Ishan Gupta 2021-06-28 17:00:34 +05:30 committed by GitHub
parent eca531afdd
commit 48d3f9ce7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 2736 additions and 2020 deletions

View File

@ -0,0 +1,3 @@
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.7" d="M33.918 24.6262C32.0531 24.6262 30.4775 25.885 29.9915 27.5981H26.0508V23.2475H30.0744C34.4446 23.2475 38 19.6883 38 15.3134C38 11.8056 35.6761 8.72413 32.3769 7.7205C31.3796 3.72291 27.775 0.855778 23.6002 0.855778C21.9819 0.855778 20.4316 1.27356 19.0635 2.07176C17.6145 0.74522 15.7146 0 13.7183 0C9.43639 0 5.93668 3.41709 5.797 7.67012C2.4081 8.6184 0 11.7246 0 15.3134C0 19.6883 3.55538 23.2475 7.9256 23.2475H11.9492V27.5981H8.0085C7.52259 25.885 5.94693 24.6262 4.08203 24.6262C1.8312 24.6262 0 26.4593 0 28.7126C0 30.9659 1.8312 32.7991 4.08203 32.7991C5.94693 32.7991 7.52252 31.5402 8.0085 29.8271H13.0625C13.6773 29.8271 14.1758 29.3281 14.1758 28.7126V23.2475H17.8867V29.9828C16.1755 30.4693 14.918 32.0466 14.918 33.9135C14.918 36.1668 16.7492 38 19 38C21.2508 38 23.082 36.1668 23.082 33.9135C23.082 32.0466 21.8245 30.4693 20.1133 29.9828V23.2475H23.8242V28.7126C23.8242 29.3281 24.3227 29.8271 24.9375 29.8271H29.9915C30.4774 31.5402 32.0531 32.7991 33.918 32.7991C36.1688 32.7991 38 30.9659 38 28.7126C38 26.4593 36.1688 24.6262 33.918 24.6262ZM4.08203 30.5701C3.05893 30.5701 2.22656 29.7368 2.22656 28.7126C2.22656 27.6884 3.05893 26.8551 4.08203 26.8551C5.10514 26.8551 5.9375 27.6884 5.9375 28.7126C5.9375 29.7368 5.10514 30.5701 4.08203 30.5701ZM20.8555 33.9135C20.8555 34.9378 20.0231 35.771 19 35.771C17.9769 35.771 17.1445 34.9378 17.1445 33.9135C17.1445 32.8893 17.9769 32.0561 19 32.0561C20.0231 32.0561 20.8555 32.8893 20.8555 33.9135ZM7.9256 21.0186C4.78318 21.0186 2.22656 18.4593 2.22656 15.3134C2.22656 12.5016 4.32005 10.0748 7.09606 9.6684C7.68186 9.58266 8.09808 9.05291 8.04346 8.46282C8.02735 8.28874 8.01919 8.11087 8.01919 7.93411C8.01919 4.78836 10.5758 2.22905 13.7182 2.22905C15.3948 2.22905 16.9759 2.95867 18.0559 4.23082C18.4351 4.67743 19.095 4.75366 19.5659 4.40534C20.7335 3.54139 22.1285 3.08475 23.6002 3.08475C26.9866 3.08475 29.8028 5.57221 30.3293 8.80801C30.4037 9.26555 30.7534 9.62917 31.2072 9.72115C33.853 10.2574 35.7734 12.6092 35.7734 15.3134C35.7734 18.4593 33.2168 21.0186 30.0743 21.0186H7.9256ZM33.918 30.5701C32.8949 30.5701 32.0625 29.7368 32.0625 28.7126C32.0625 27.6884 32.8949 26.8551 33.918 26.8551C34.9411 26.8551 35.7734 27.6884 35.7734 28.7126C35.7734 29.7368 34.9411 30.5701 33.918 30.5701Z" fill="#DBA017"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,5 @@
<svg width="42" height="21" viewBox="0 0 42 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="19.4121" y="7.00098" width="5.64705" height="6.65" fill="#DBA017"/>
<path d="M20.1764 10.5C20.1764 15.7388 15.8915 20 10.5882 20C5.28488 20 1 15.7388 1 10.5C1 5.26117 5.28488 1 10.5882 1C15.8915 1 20.1764 5.26117 20.1764 10.5Z" stroke="#DBA017" stroke-width="2"/>
<rect x="29.2939" y="7.00098" width="12.7059" height="6.65" fill="#DBA017"/>
</svg>

After

Width:  |  Height:  |  Size: 453 B

View File

@ -1,6 +1,6 @@
<svg width="63" height="65" viewBox="0 0 63 65" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect y="0.5" width="63" height="64" rx="31" fill="white"/>
<rect y="0.5" width="63" height="64" rx="31" fill="url(#pattern0)"/>
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="19" height="19" rx="9.5" fill="white"/>
<rect width="19" height="19" rx="9.5" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="translate(-0.00969232 -0.00989646) scale(0.000538194)"/>

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View File

@ -0,0 +1,6 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.11642 7.00293H4.88667C4.70008 7.00293 4.54883 7.15256 4.54883 7.33716V11.6654C4.54883 11.85 4.70008 11.9996 4.88667 11.9996H7.11642C7.30301 11.9996 7.45427 11.85 7.45427 11.6654V7.33716C7.45427 7.15256 7.30301 7.00293 7.11642 7.00293ZM6.77858 11.3312H5.22451V7.67139H6.77858V11.3312Z" fill="#1C0732"/>
<path d="M11.5415 4.37744H9.29487C9.10828 4.37744 8.95703 4.52708 8.95703 4.71167V11.6636C8.95703 11.8482 9.10828 11.9979 9.29487 11.9979H11.5415C11.7281 11.9979 11.8794 11.8482 11.8794 11.6636V4.71167C11.8794 4.52711 11.7281 4.37744 11.5415 4.37744ZM11.2037 11.3294H9.63272V5.0459H11.2037V11.3294Z" fill="#1C0732"/>
<path d="M2.70755 8.62451H0.46089C0.274299 8.62451 0.123047 8.77415 0.123047 8.95874V11.666C0.123047 11.8506 0.274299 12.0002 0.46089 12.0002H2.70755C2.89414 12.0002 3.04539 11.8506 3.04539 11.666V8.95874C3.04539 8.77418 2.89414 8.62451 2.70755 8.62451ZM2.36971 11.3318H0.798732V9.29297H2.36971V11.3318Z" fill="#1C0732"/>
<path d="M11.9129 0.335185C11.8615 0.265134 11.7807 0.222079 11.6933 0.218209L8.61893 0.000954961C8.43233 -0.0128953 8.26973 0.125543 8.25573 0.310107C8.24173 0.494702 8.38164 0.655564 8.56823 0.669414L10.7441 0.815593L6.7439 3.91146L3.68643 1.53843C3.55587 1.43614 3.36948 1.44324 3.24723 1.55513L0.105313 4.52974C-0.0282527 4.65509 -0.0357539 4.8632 0.0884354 4.99767C0.148703 5.07505 0.243179 5.11867 0.341826 5.11464C0.430934 5.1134 0.515936 5.07735 0.578306 5.01437L3.50065 2.24025L6.52433 4.59656C6.64774 4.69431 6.82321 4.69431 6.94662 4.59656L11.2711 1.26744L11.1021 3.34325C11.1012 3.52474 11.2404 3.6769 11.423 3.69418H11.4399C11.6141 3.69504 11.7603 3.56479 11.7778 3.39337L11.9974 0.569136C12.0097 0.482164 11.9782 0.394712 11.9129 0.335185Z" fill="#1C0732"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -25,7 +25,7 @@ getStarted:
sidebar:
title: Litmus
version: 'Version: 1.13'
version: "Version: 1.13"
header:
projectDropdown:
@ -34,7 +34,7 @@ header:
otherProjects: Other Projects
noProjectsOther: You are not part of any projects not owned by you
profileDropdown:
signedIn: 'Signed in as:'
signedIn: "Signed in as:"
emailUnset: Email not set
emailSet: Set up your email
switchProject: Please switch to a project you own to access settings
@ -53,8 +53,8 @@ quickActionCard:
quickSurvey: Take a quick survey
readDocs: Read the Litmus docs
readAPIDocs: Read the Litmus Portal API docs
addDashboard: Add a Dashboard
addDataSource: Add a Data Source
addDashboard: Create a dashboard
addDataSource: Add a data source
addAgent: Add an agent
connectNewAgent: Connect a new agent
scheduleWorkflow: Schedule a workflow
@ -89,7 +89,7 @@ workflowStepper:
back: Back
editor:
status: 'YAML Status:'
status: "YAML Status:"
######################################
############ Pages #############
@ -107,14 +107,14 @@ login:
tooltipText: You need to contact your admin to reset your password.
schedule:
heading: 'Schedule a workflow'
headingDesc: 'Click on test to see detailed log of your workflow'
headingText: 'Schedule details:'
description: 'Choose the right time to schedule your first workflow. Below you can find some options that may be convenient for you.'
save: 'Save Changes'
heading: "Schedule a workflow"
headingDesc: "Click on test to see detailed log of your workflow"
headingText: "Schedule details:"
description: "Choose the right time to schedule your first workflow. Below you can find some options that may be convenient for you."
save: "Save Changes"
scheduleNow: Schedule now
scheduleAfter: Schedule after some time
scheduleAfterSometime: 'Choose the minutes, hours, or days when you want to start workflow'
scheduleAfterSometime: "Choose the minutes, hours, or days when you want to start workflow"
scheduleSpecificTime: Schedule at a specific time
scheduleFuture: Select date and time to start workflow in future
scheduleRecurring: Recurring Schedule
@ -122,7 +122,7 @@ schedule:
after: After
at: at
At: At
scheduleOn: 'On'
scheduleOn: "On"
missingPerm: Missing sufficient permissions :(
requiredPerm: Looks like you do not have the required permission to create a new workflow on this project.
contact: Contact portal administrator to upgrade your permission.
@ -143,11 +143,11 @@ editSchedule:
successfullyCreated: is successfully created
community:
title: 'Community'
heading: 'Litmus Insights'
headingDesc: 'Stats for the Litmus community'
analyticDesc: 'Periodic growth of Litmus'
statsHeading: 'Litmus users around the world:'
title: "Community"
heading: "Litmus Insights"
headingDesc: "Stats for the Litmus community"
analyticDesc: "Periodic growth of Litmus"
statsHeading: "Litmus users around the world:"
litmusChaos: Litmuschaos
follow: Follow
@ -158,8 +158,8 @@ analytics:
comingSoon: Analytics Coming Soon !
waitingMessage: Waiting for workflow to start running !
chaosCompleteWaitingMessage: Waiting for chaos experiment to finish !
highestScore: 'Highest Score:'
lowestScore: 'Lowest Score:'
highestScore: "Highest Score:"
lowestScore: "Lowest Score:"
exportPDF: Export PDF
compareWorkflows: Compare workflows
targetCluster: Target cluster
@ -200,6 +200,7 @@ analyticsDashboard:
subHeading1: Dashboard Configuration Details
subHeading2: Selected Applications
subHeading3: Selected Metrics
infoKeyNamespace: Namespace
metaData1: Name
metaData2: Type
metaData3: DataSource
@ -221,12 +222,12 @@ analyticsDashboard:
every2Hours: Every 2 hours
every1Day: Every 1 day
chaosTable:
tableHead1: Legend
tableHead1: Chaos result name
tableHead2: Workflow
tableHead3: Experiment
tableHead4: "Target \n ( namespace / pod )"
tableHead3: Engine context
tableHead4: Target
tableHead5:
title: Result
title: Verdict
infoText: Latest Chaos experiment verdict
noRecords: No chaos happened during the interval
showTable: Show Chaos during this interval
@ -264,14 +265,41 @@ analyticsDashboard:
workflowScheduleTable:
title: Workflow Schedules
noRecordMessage: No workflow has been schedule
kubernetesDashboardTable:
title: Application Dashboard
noRecordMessage: No dashboard has been configured
workflowDashboard: Workflow Dashboards
workflowDashboard: Workflow dashboards
overviewTabdataSourceTable: Connected Data Sources
applicationDashboard: Application Dashboards
applicationDashboard: Application dashboards
applicationDashboardTable:
warning:
noActiveDataSource: No active data source found for creating an application dashboard.
noAvailableDataSource: No data source available. To create an Application dashboard you need to add a data source.
configureExisting: Configure an existing data source
addNew: Add data source
or: or
deletionSuccess: Successfully deleted the dashboard
deletionError: Error while deleting the dashboard
search: Search
selectPeriod: Select period
all: All
tableHead1: Dashboard name
tableHead2: Agent name
tableHead3: Dashboard type
tableHead4: Data source type
tableHead5: Last viewed
createDashboard: Create dashboard
title: Application Dashboard
view: View
configure: Configure
delete: Delete
noRecords: No dashboard available
loading: Loading available dashboards
error: Unable to fetch data
modal:
removeDashboard: Remove dashboard ?
removeDashboardConfirmation: Are you sure you want to remove the dashboard
cancel: Cancel
delete: Delete
applicationDashboards:
createHeader: Create a new dashboard
createHeader: Create dashboard
configureHeader: Configure dashboard
chooseADashboardType:
header: Select a dashboard type
@ -288,7 +316,11 @@ analyticsDashboard:
form:
name: Name
agent: Agent
noActiveAgent: No active agent found
agentInactive: Agent is inactive
dataSource: Data source
noActiveDataSource: No active data source found
dataSourceInactive: Data source is inactive
dashboardType: Dashboard type
applications: Applications
selectNamespaces: Select namespaces
@ -333,6 +365,14 @@ analyticsDashboard:
lineGraph: Line graph
resolution: Resolution
resolutionInfo: Resolution
removeMetric: Remove metric ?
removeMetricConfirmation: Are you sure you want to remove the metric panel
under: under
cancel: Cancel
delete: Delete
discardChangesConfirmation: Are you sure to discard changes for the metric panel
discardChangesInfo: The selected metric panel will be reverted back to the previous version
yesProceed: Yes, Proceed
stepper:
steps:
step1: Choose a dashboard type
@ -367,14 +407,8 @@ analyticsDashboard:
of2: of 2
saveChanges: Save changes
next: Next
modalHeadingSuccessAdd: A new data source is successfully connected
modalHeadingSuccessConfigure: Data source information is successfully updated
modalHeadingFailed: There was a problem with connecting your data source
modalBodySuccessAdd: You will see the data source connected in the data source table.
modalBodySuccessConfigure: You will see the data source information updated in the data source table.
modalBodyFailed: Try back again or check your entered details.
modalActionsBack: Back to Data Source
modalActionsTryAgain: Try Again
adding: Adding
updating: Updating
general: General
generalInfo: Provide a name and choose the data source type
name: Name
@ -398,22 +432,39 @@ analyticsDashboard:
scrapeInterval: Scrape Interval
queryTimeOut: Query timeout
httpMethod: HTTP Method
connectionSuccess: Successfully connected to the data source
connectionError: Error connecting to the data source
updateSuccess: Successfully updated the data source information
updateError: Error updating the data source information
dataSourceTable:
search: Search
selectPeriod: Select period
all: All
warning:
text: Unexpected bad things will happen if you dont read this!
info: The data source seems to be connected to following Application dashboards. Dashboards will be unable to show metrics if the data source is deleted.
connectedDashboards: "Connected Dashboards :"
showLess: Show Less Dashboards
dashboards: Dashboards
deletionSuccess: Successfully deleted the data source
deletionError: Error while deleting the data source
tableHead1: Status
tableHead2: Data Source Name
tableHead3: Data Source Type
tableHead4: Last Configured
addDataSource: Add a Data Source
tableHead2: Data source name
tableHead3: Data source type
tableHead4: Last configured
tableHead5: Data source link
addDataSource: Add data source
loading: Loading available data sources
noRecords: No data source available
fetchError: Unable to fetch data
dashboardTable:
tableHead1: Dashboard Name
tableHead2: Agent Name
tableHead3: Dashboard Type
tableHead4: Data Source Type
tableHead5: Last viewed
addDashboard: Add a Dashboard
configure: Configure
delete: Delete
modal:
removeDataSource: Remove data source ?
removeDataSourceConfirmation: Are you sure you want to remove the data source
cancel: Cancel
delete: Delete
forceDelete: Force Delete
glowCard:
title-0: Set it up
heading-0: Add a Data Source
@ -429,10 +480,10 @@ analyticsDashboard:
configureBanner:
case-0:
heading: No data source is configured yet for any agent
description: To configure your first Kubernetes dashboard you need to connect a data source. Select "Add a data source" to connect.
description: To configure your first application dashboard you need to connect a data source. Select "Add a data source" to connect.
case-1:
heading: Data sources has been added, now configure a dashboard
description: To configure your first Kubernets dashboard use the connect Data sources of this project
heading: Data source(s) has been added, now configure a dashboard
description: To configure your first application dashboard use the connect data sources of this project.
timeText:
lastRun: Last run
lastOpened: Last opened
@ -496,8 +547,8 @@ homeViews:
recentWorkflowRuns:
workflowRunCard:
cardTitle: Browse workflow
resilienceRate: 'Overall resilience rate:'
lastRun: 'Last Run:'
resilienceRate: "Overall resilience rate:"
lastRun: "Last Run:"
showAnalytics: Show the analytics
downloadManifest: Download manifest
heading: Recent Workflow runs
@ -540,7 +591,7 @@ chaosWorkflows:
disableSchedule: Disable Schedule
enableSchedule: Enable Schedule
saveTemplate: Save Template
updateEngine: 'Note: Make sure to update the chaos engine names'
updateEngine: "Note: Make sure to update the chaos engine names"
saveChanges: Save Changes
savedSuccessfully: Successfully saved template.
cancel: Cancel
@ -562,24 +613,13 @@ chaosWorkflows:
sync: Sync Workflow
terminate: Terminate Workflow
tableData:
overallRR: 'Overall RR : '
experimentsPassed: 'Experiments Passed : '
overallRR: "Overall RR : "
experimentsPassed: "Experiments Passed : "
showExperiments: Show Experiments
showTheWorkflow: Show the workflow
showTheAnalytics: Show the analytics
na: NA
analyticsDashboardViews:
kubernetesDashboard:
form:
dashboardPanels: Dashboard Panels
table:
seeAnalytics: See Analytics
configure: Configure
noRecords: No dashboard available
loading: Loading available dashboards
error: Unable to fetch data
settings:
accountsTab:
personalDetails:
@ -763,7 +803,7 @@ settings:
disconnect: Are you sure you want to disconnect?
save: Save Locally
repo: Git Repository
desc: '* Any existing or active workflows saved locally will not be synced into the git repository'
desc: "* Any existing or active workflows saved locally will not be synced into the git repository"
connect: Connect
branch: Branch
URL: Git URL
@ -778,11 +818,11 @@ settings:
headingDesc: Your image registry for the workflow manifest
defaultValues: Use the default values
defaultText: All YAML files use these default values provided by Litmus. The values cannot be changed.
registry: 'Registry : '
repo: 'Repository : '
repoType: 'Registry Type : '
dockerio: 'docker.io'
litmus: 'litmuschaos'
registry: "Registry : "
repo: "Repository : "
repoType: "Registry Type : "
dockerio: "docker.io"
litmus: "litmuschaos"
public: Public
private: Private
defaultReg: Use Default Registry
@ -814,12 +854,12 @@ workflowDetailsView:
Completed: Completed
runTime:
runTimeHeader: Run Time
startTime: 'Start Time :'
endTime: 'End Time :'
startTime: "Start Time :"
endTime: "End Time :"
targets:
targetsHeader: Target
cluster: 'Cluster :'
namespace: 'Workflow Namespace :'
cluster: "Cluster :"
namespace: "Workflow Namespace :"
workflowNodeInfo:
name: Name
type: Type
@ -874,8 +914,8 @@ createWorkflow:
revertSchedule: Revert Schedule
noTemplates: No template available.
addTemplate: You can add a template from the scheduled workflows table.
trueValue: 'True'
falseValue: 'False'
trueValue: "True"
falseValue: "False"
table:
head1: Sequence
head2: Name
@ -970,8 +1010,8 @@ createWorkflow:
myHubInfo: Experiment details
annotationInfo: Provide target application details where you want to induce the chaos.
annotation: Annotation Check
true: 'True'
false: 'False'
true: "True"
false: "False"
annotationDesc: The target application does not have an annotation “Chaos=true”. Ideally, you cannot run this experiment unless the annotation is patched to the application. However, this service account has the privileges to disable the enforcement of annotation checks. Mark the annotation as false to proceed.
appkind: appKind
deployment: Deployment
@ -980,7 +1020,7 @@ createWorkflow:
deploymentconfig: Deploymentconfig
rollout: Rollout
nodeselector: NodeSelector
selector: 'kubernetes.io/hostname :'
selector: "kubernetes.io/hostname :"
nsError: Namespace not available
labelError: Label not available in the resource or namespace
deleteExp: Delete the experiment
@ -994,7 +1034,7 @@ createWorkflow:
showDetails: Show Details
showProp: Show Properties
addProbe: Please add probes to see the data
addNewProbe: '+ Add a new Probe'
addNewProbe: "+ Add a new Probe"
back: Back
finish: Finish
addProbe:
@ -1176,7 +1216,7 @@ browseTemplate:
#########################################
internetIssues:
fetchData: 'Fetching the data...'
fetchData: "Fetching the data..."
connectionError: It seems you have no internet connection, Please try again When connectivity resumes.
######################################
@ -1186,14 +1226,14 @@ myhub:
title: MyHubs
connectTarget: Connect the target
viewMyHub: View My Hub
error: '[Error: could not connect]'
error: "[Error: could not connect]"
view: View
validationEmptySpace: Should not start with an empty space
validURL: Enter a valid URL
edit: Edit Hub
refresh: Refresh Hub
disconnect: Disconnect Hub
lastSync: 'Last synced at:'
lastSync: "Last synced at:"
mainPage:
header: ChaosHubs
github: github.com/
@ -1221,9 +1261,9 @@ myhub:
chaosCharts: Chaos-charts
noPredefinedExp: No predefined workflows available with information in this Hub
noExp: No experiments found
lastSynced: 'Last synced at: '
repoLink: 'Repository Link: '
repoBranch: 'Repository Branch: '
lastSynced: "Last synced at: "
repoLink: "Repository Link: "
repoBranch: "Repository Branch: "
connectHubPage:
connectHub: Connect a new chaos hub
editHub: Edit hub configuration
@ -1241,7 +1281,7 @@ myhub:
accessToken: Access Token
ssh: SSH
sshText: Please enable read / write access on the above git repository for the following key
sshAlert: '*Make sure to provide the SSH link of the Git Repository'
sshAlert: "*Make sure to provide the SSH link of the Git Repository"
saveLater: Save for later
updateHub: Update the MyHub with correct details from the MyHubs section
cancel: Cancel

View File

@ -48,10 +48,6 @@ const StyledTab = withStyles((theme) =>
wrapper: {
flexDirection: 'row-reverse',
},
labelContainer: {
width: 'auto',
padding: 0,
},
})
)((props: StyledTabProps) => <Tab {...props} />);

View File

@ -237,6 +237,7 @@ const Routes: React.FC = () => {
to="/analytics"
/>
<Redirect exact path="/analytics/datasource" to="/analytics" />
<Redirect exact path="/analytics/dashboard" to="/analytics" />
<Redirect exact path="/api-doc" to="/api-doc/index.html" />
<Redirect to="/404" />
</Switch>

View File

@ -14,6 +14,7 @@ const useStyles = makeStyles((theme: Theme) => ({
'& ::-webkit-scrollbar': {
width: '0.4rem',
height: '0.4rem',
},
'& ::-webkit-scrollbar-track': {
marginTop: theme.spacing(1),

View File

@ -84,6 +84,7 @@ export interface DashboardDetails {
export interface DashboardConfigurationDetails {
name: string;
typeID: string;
typeName: string;
dataSourceName: string;
dataSourceURL: string;
agentName: string;
@ -97,10 +98,12 @@ export interface PanelNameAndID {
export interface GraphPanelProps extends PanelResponse {
className?: string;
controllerPanelID?: string;
selectedApplications?: string[];
}
export interface GraphPanelGroupProps extends PanelGroupResponse {
selectedPanels?: string[];
selectedApplications?: string[];
}
export interface ParsedPrometheusData {
@ -108,34 +111,15 @@ export interface ParsedPrometheusData {
closedAreaData: Array<GraphMetric>;
chaosData: Array<GraphMetric>;
}
export interface RunWiseChaosMetrics {
runIndex: number;
runID: string;
lastUpdatedTimeStamp: number;
probeSuccessPercentage: string;
experimentStatus: string;
experimentVerdict: string;
resilienceScore: string;
workflowStatus: string;
}
export interface WorkflowAndExperimentMetaDataMap {
workflowID: string;
workflowName: string;
experimentName: string;
targetApp: string;
targetNamespace: string;
runWiseChaosMetrics: RunWiseChaosMetrics[];
}
export interface ChaosEventDetails {
id: string;
legend: string;
legendColor: string;
chaosResultName: string;
workflow: string;
experiment: string;
engineContext: string;
target: string;
result: string;
chaosMetrics: WorkflowAndExperimentMetaDataMap;
showOnTable: Boolean;
verdict: string;
}
export interface SelectedDashboardInformation {

View File

@ -28,6 +28,7 @@ import * as DashboardActions from '../../redux/actions/dashboards';
import * as DataSourceActions from '../../redux/actions/dataSource';
import { RootState } from '../../redux/reducers';
import { getProjectID } from '../../utils/getSearchParams';
import ChaosAccordion from '../../views/Analytics/ApplicationDashboard/ChaosAccordion';
import DataSourceInactiveModal from '../../views/Analytics/ApplicationDashboard/DataSourceInactiveModal';
import InfoDropdown from '../../views/Analytics/ApplicationDashboard/InfoDropdown';
import DashboardPanelGroup from '../../views/Analytics/ApplicationDashboard/Panel/DashboardPanelGroup';
@ -79,6 +80,9 @@ const DashboardPage: React.FC = () => {
};
const [isInfoOpen, setIsInfoOpen] = React.useState<Boolean>(false);
const [selectedPanels, setSelectedPanels] = React.useState<string[]>([]);
const [selectedApplications, setSelectedApplications] = React.useState<
string[]
>([]);
// Apollo query to get the dashboards data
const { data: dashboards } = useQuery<DashboardList, ListDashboardVars>(
@ -98,6 +102,8 @@ const DashboardPage: React.FC = () => {
}
);
const postEventSelectionRoutine = (selectedEvents: string[]) => {};
useEffect(() => {
if (dashboards && dashboards.ListDashboard.length) {
if (
@ -291,18 +297,21 @@ const DashboardPage: React.FC = () => {
dashboardConfigurationDetails={{
name: selectedDashboardInformation.name,
typeID: selectedDashboardInformation.typeID,
typeName: selectedDashboardInformation.typeName,
dataSourceName: selectedDataSource.selectedDataSourceName,
dataSourceURL: selectedDataSource.selectedDataSourceURL,
agentName: selectedDashboardInformation.agentName,
}}
metricsToBeShown={selectedDashboardInformation.panelNameAndIDList}
applicationsToBeShown={[]}
postPanelSelectionRoutine={(selectedPanelList: string[]) => {
setSelectedPanels(selectedPanelList);
}}
applicationsToBeShown={
selectedDashboardInformation.applicationMetadataMap
}
postPanelSelectionRoutine={(selectedPanelList: string[]) =>
setSelectedPanels(selectedPanelList)
}
postApplicationSelectionRoutine={(
selectedApplicationList: string[]
) => {}}
) => setSelectedApplications(selectedApplicationList)}
/>
)}
<ToolBar />
@ -310,13 +319,13 @@ const DashboardPage: React.FC = () => {
className={classes.analyticsDiv}
key={selectedDashboardInformation.dashboardKey}
>
{/* <div className={classes.chaosTableSection}>
<div className={classes.chaosTableSection}>
<ChaosAccordion
dashboardKey={selectedDashboardInformation.dashboardKey}
chaosEventsToBeShown={prometheusQueryData?.chaosEventsToBeShown}
chaosEventsToBeShown={[]}
postEventSelectionRoutine={postEventSelectionRoutine}
/>
</div> */}
</div>
{selectedDashboardInformation.metaData[0] &&
selectedDashboardInformation.metaData[0].panel_groups.length >
0 &&
@ -332,6 +341,7 @@ const DashboardPage: React.FC = () => {
panel_group_name={panelGroup.panel_group_name}
panels={panelGroup.panels}
selectedPanels={selectedPanels}
selectedApplications={selectedApplications}
/>
</div>
)

View File

@ -92,7 +92,13 @@ const ChooseAndConfigureDashboards: React.FC<ChooseAndConfigureDashboardsProps>
const promQueries: PromQueryDetails[] = [];
panel.prom_queries.forEach((promQuery) => {
promQueries.push({
...promQuery,
queryid: promQuery.queryid,
prom_query_name: promQuery.prom_query_name,
legend: promQuery.legend,
resolution: promQuery.resolution,
minstep: promQuery.minstep,
line: promQuery.line,
close_area: promQuery.close_area,
});
});
const panelOption: PanelOption = {
@ -101,7 +107,11 @@ const ChooseAndConfigureDashboards: React.FC<ChooseAndConfigureDashboardsProps>
left_axis: panel.panel_options.left_axis,
};
panels.push({
...panel,
panel_name: panel.panel_name,
y_axis_left: panel.y_axis_left,
y_axis_right: panel.y_axis_right,
x_axis_down: panel.x_axis_down,
unit: panel.unit,
panel_options: panelOption,
prom_queries: promQueries,
panel_id: panel.panel_id,

View File

@ -1,10 +1,12 @@
import { useMutation } from '@apollo/client';
import { Typography } from '@material-ui/core';
import { ButtonFilled, ButtonOutlined, Modal } from 'litmus-ui';
import { Snackbar, Typography } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { ButtonFilled, ButtonOutlined } from 'litmus-ui';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import BackButton from '../../components/Button/BackButton';
import Loader from '../../components/Loader';
import Scaffold from '../../containers/layouts/Scaffold';
import { CREATE_DATASOURCE, UPDATE_DATASOURCE } from '../../graphql/mutations';
import { DataSourceDetails } from '../../models/dataSourceData';
@ -14,9 +16,12 @@ import {
} from '../../models/graphql/dataSourceDetails';
import { history } from '../../redux/configureStore';
import { RootState } from '../../redux/reducers';
import { ReactComponent as CrossMarkIcon } from '../../svg/crossmark.svg';
import { getProjectID, getProjectRole } from '../../utils/getSearchParams';
import { isValidWebUrl, validateTimeInSeconds } from '../../utils/validate';
import {
isValidWebUrl,
validateTextEmpty,
validateTimeInSeconds,
} from '../../utils/validate';
import ConfigurePrometheus from '../../views/Analytics/DataSources/Forms/prometheus';
import useStyles from './styles';
@ -37,7 +42,6 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
);
const projectID = getProjectID();
const projectRole = getProjectRole();
const [open, setOpen] = React.useState(false);
const [disabled, setDisabled] = React.useState(true);
const [page, setPage] = React.useState<number>(1);
const [dataSourceVars, setDataSourceVars] = useState<DataSourceDetails>({
@ -58,19 +62,21 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
httpMethod: 'POST',
});
const [mutate, setMutate] = React.useState(false);
const [isAlertOpen, setIsAlertOpen] = React.useState(false);
const [success, setSuccess] = React.useState(false);
const [createDataSource] = useMutation<
ListDataSourceResponse,
CreateDataSourceInput
>(CREATE_DATASOURCE, {
onCompleted: () => {
setSuccess(true);
setMutate(false);
setOpen(true);
setSuccess(true);
setIsAlertOpen(true);
},
onError: () => {
setMutate(false);
setOpen(true);
setSuccess(false);
setIsAlertOpen(true);
},
});
const [updateDataSource] = useMutation<
@ -78,13 +84,14 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
CreateDataSourceInput
>(UPDATE_DATASOURCE, {
onCompleted: () => {
setSuccess(true);
setMutate(false);
setOpen(true);
setSuccess(true);
setIsAlertOpen(true);
},
onError: () => {
setMutate(false);
setOpen(true);
setSuccess(false);
setIsAlertOpen(true);
},
});
@ -160,7 +167,10 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
dataSourceVars.name === '' ||
!isValidWebUrl(dataSourceVars.url) ||
!validateTimeInSeconds(dataSourceVars.queryTimeout) ||
!validateTimeInSeconds(dataSourceVars.scrapeInterval)
!validateTimeInSeconds(dataSourceVars.scrapeInterval) ||
(dataSourceVars.basicAuth &&
(validateTextEmpty(dataSourceVars.username) ||
validateTextEmpty(dataSourceVars.password)))
) {
setDisabled(true);
} else {
@ -193,8 +203,8 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
<BackButton />
</div>
<Typography className={classes.heading}>
{t('analyticsDashboard.dataSourceForm.headingConfigure')} /{' '}
{selectedDataSourceName}
{t('analyticsDashboard.dataSourceForm.headingConfigure')} /
{` ${selectedDataSourceName}`}
</Typography>
{dataSourceID ? (
<ConfigurePrometheus
@ -211,7 +221,9 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
</div>
)}
<div className={classes.buttons}>
<div
className={`${classes.buttons} ${page === 2 ? '' : classes.flexEnd}`}
>
{page === 2 && (
<ButtonOutlined onClick={() => setPage(1)} disabled={false}>
<Typography>
@ -221,13 +233,16 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
)}
<div className={classes.saveButton}>
<Typography className={classes.stepText}>
{t('analyticsDashboard.dataSourceForm.step')}{' '}
<strong>{page}</strong>{' '}
{t('analyticsDashboard.dataSourceForm.step')}
<strong>{` ${page} `}</strong>
{t('analyticsDashboard.dataSourceForm.of2')}
</Typography>
<ButtonFilled
disabled={
page === 1 && dataSourceVars.name === ''
(page === 1 &&
(dataSourceVars.name === '' ||
!isValidWebUrl(dataSourceVars.url))) ||
mutate
? true
: page === 2
? disabled
@ -237,124 +252,56 @@ const DataSourceConfigurePage: React.FC<DataSourceConfigurePageProps> = ({
page === 2 && !disabled ? setMutate(true) : setPage(2)
}
>
<Typography>
<Typography className={classes.buttonText}>
{page === 2
? `${t('analyticsDashboard.dataSourceForm.saveChanges')}`
: `${t('analyticsDashboard.dataSourceForm.next')}`}
? mutate
? !configure
? t('analyticsDashboard.dataSourceForm.adding')
: t('analyticsDashboard.dataSourceForm.updating')
: t('analyticsDashboard.dataSourceForm.saveChanges')
: t('analyticsDashboard.dataSourceForm.next')}
</Typography>
{mutate && <Loader size={20} />}
</ButtonFilled>
</div>
</div>
</div>
<Modal
open={open}
onClose={() => setOpen(false)}
width="60%"
modalActions={
<ButtonOutlined
className={classes.closeButton}
onClick={() => setOpen(false)}
>
&#x2715;
</ButtonOutlined>
}
>
<div className={classes.modal}>
<Typography align="center">
{success === true ? (
<img
src="/icons/finish.svg"
alt="success"
className={classes.icon}
/>
) : (
<CrossMarkIcon className={classes.icon} />
)}
</Typography>
<Typography
className={classes.modalHeading}
align="center"
variant="h3"
>
{success === true && configure === false
? `${t(
'analyticsDashboard.dataSourceForm.modalHeadingSuccessAdd'
)}`
: success === true && configure === true
? `${t(
'analyticsDashboard.dataSourceForm.modalHeadingSuccessConfigure'
)}`
: `${t('analyticsDashboard.dataSourceForm.modalHeadingFailed')}`}
</Typography>
<Typography
align="center"
variant="body1"
className={classes.modalBody}
>
{success === true && configure === false ? (
<div>
{t('analyticsDashboard.dataSourceForm.modalBodySuccessAdd')}
</div>
) : success === true && configure === true ? (
<div>
{t(
'analyticsDashboard.dataSourceForm.modalBodySuccessConfigure'
)}
</div>
) : (
<div>
{t('analyticsDashboard.dataSourceForm.modalBodyFailed')}
</div>
)}
</Typography>
{success === true ? (
<ButtonFilled
variant="success"
onClick={() => {
{isAlertOpen && (
<Snackbar
open={isAlertOpen}
autoHideDuration={6000}
onClose={() => {
setIsAlertOpen(false);
if (success) {
history.push({
pathname: '/analytics',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}
}}
>
<Alert
onClose={() => {
setIsAlertOpen(false);
if (success) {
history.push({
pathname: '/analytics',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
>
<div>
{t('analyticsDashboard.dataSourceForm.modalActionsBack')}
</div>
</ButtonFilled>
) : (
<div className={classes.flexButtons}>
<ButtonOutlined
onClick={() => {
setOpen(false);
setMutate(true);
}}
disabled={false}
>
<div>
{t('analyticsDashboard.dataSourceForm.modalActionsTryAgain')}
</div>
</ButtonOutlined>
<ButtonFilled
variant="error"
onClick={() => {
history.push({
pathname: '/analytics',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
>
<div>
{t('analyticsDashboard.dataSourceForm.modalActionsBack')}
</div>
</ButtonFilled>
</div>
)}
</div>
</Modal>
}
}}
severity={success ? 'success' : 'error'}
>
{!configure
? success
? t('analyticsDashboard.dataSourceForm.connectionSuccess')
: t('analyticsDashboard.dataSourceForm.connectionError')
: success
? t('analyticsDashboard.dataSourceForm.updateSuccess')
: t('analyticsDashboard.dataSourceForm.updateError')}
</Alert>
</Snackbar>
)}
</Scaffold>
);
};

View File

@ -49,6 +49,10 @@ const useStyles = makeStyles((theme) => ({
paddingBottom: theme.spacing(7.5),
},
flexEnd: {
justifyContent: 'flex-end',
},
saveButton: {
display: 'flex',
},
@ -65,6 +69,10 @@ const useStyles = makeStyles((theme) => ({
flexDirection: 'row',
justifyContent: 'space-evenly',
},
buttonText: {
paddingRight: theme.spacing(1),
},
}));
export default useStyles;

View File

@ -1,14 +1,6 @@
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M14.6073 1.97255C14.3315 1.67929 13.944 1.51172 13.539 1.51172C12.7326 1.51172 12.0797 2.16457 12.0797 2.97103C12.0797 3.1002 12.0972 3.22938 12.1321 3.35506L10.5506 4.47572C9.985 3.90317 9.05984 3.89619 8.48729 4.46176C8.08231 4.86324 7.94615 5.46023 8.14166 5.99788L6.68235 7.22677C6.22151 6.56694 5.31381 6.40285 4.65049 6.86369C4.39912 7.03824 4.2106 7.28961 4.10935 7.57588L2.89792 7.08363C2.76176 6.28764 2.01116 5.75349 1.21517 5.88965C0.419185 6.0258 -0.114964 6.77641 0.0211916 7.57239C0.157347 8.36838 0.907949 8.90253 1.70394 8.76638C2.22761 8.6791 2.66052 8.31252 2.83857 7.81328L4.05001 8.30554C4.18616 9.10153 4.93676 9.63568 5.73275 9.49952C6.43448 9.38082 6.94768 8.77336 6.94768 8.06116C6.94768 8.01577 6.94419 7.9669 6.9407 7.92151L8.53267 6.57741C9.12966 7.11854 10.0513 7.07665 10.5925 6.47966C10.9381 6.09912 11.0568 5.56497 10.9102 5.07621L12.4742 3.96601C13.0258 4.55602 13.9475 4.58395 14.5375 4.03584C15.1275 3.48423 15.1554 2.56256 14.6073 1.97255ZM1.45955 8.09258C1.04061 8.09258 0.698479 7.75044 0.698479 7.3315C0.698479 6.91256 1.04061 6.57043 1.45955 6.57043C1.8785 6.57043 2.22063 6.91256 2.22063 7.3315C2.22063 7.75044 1.8785 8.09258 1.45955 8.09258ZM5.48488 8.82573C5.06594 8.82573 4.7238 8.48359 4.7238 8.06465C4.7238 7.64571 5.06594 7.30357 5.48488 7.30357C5.90382 7.30357 6.24595 7.64571 6.24595 8.06465C6.24595 8.48359 5.90731 8.82573 5.48488 8.82573ZM9.51369 6.2632C9.09475 6.2632 8.75261 5.92107 8.75261 5.50213C8.75261 5.08319 9.09475 4.74105 9.51369 4.74105C9.93263 4.74105 10.2748 5.08319 10.2748 5.50213C10.2748 5.92107 9.93263 6.2632 9.51369 6.2632ZM13.539 3.7321C13.1201 3.7321 12.7779 3.38997 12.7779 2.97103C12.7779 2.55209 13.1201 2.20995 13.539 2.20995C13.958 2.20995 14.3001 2.55209 14.3001 2.97103C14.3001 3.39346 13.9614 3.7321 13.539 3.7321Z" fill="#1C0732"/>
<path d="M14.5166 5.58948H12.5615C12.3695 5.58948 12.2124 5.74658 12.2124 5.93859V14.2476C12.2124 14.4396 12.3695 14.5967 12.5615 14.5967H14.5166C14.7086 14.5967 14.8657 14.4396 14.8657 14.2476V5.93859C14.8657 5.74658 14.7086 5.58948 14.5166 5.58948ZM14.1675 13.9334H12.9106V6.28771H14.1675V13.9334Z" fill="#1C0732"/>
<path d="M10.5014 8.10315H8.54638C8.35437 8.10315 8.19727 8.26025 8.19727 8.45227V14.2476C8.19727 14.4396 8.35437 14.5967 8.54638 14.5967H10.5014C10.6935 14.5967 10.8506 14.4396 10.8506 14.2476V8.45227C10.8506 8.26025 10.6935 8.10315 10.5014 8.10315ZM10.1523 13.9334H8.8955V8.80138H10.1523V13.9334Z" fill="#1C0732"/>
<path d="M6.45188 10.7214H4.49682C4.30481 10.7214 4.14771 10.8785 4.14771 11.0705V14.2824C4.14771 14.4744 4.30481 14.6315 4.49682 14.6315H6.45188C6.6439 14.6315 6.801 14.4744 6.801 14.2824V11.0705C6.801 10.8785 6.6439 10.7214 6.45188 10.7214ZM6.10277 13.9333H4.84594V11.4196H6.10277V13.9333Z" fill="#1C0732"/>
<path d="M2.43711 9.98822H0.482052C0.290037 9.98822 0.132935 10.1453 0.132935 10.3373V14.2824C0.132935 14.4744 0.290037 14.6315 0.482052 14.6315H2.43711C2.62912 14.6315 2.78623 14.4744 2.78623 14.2824V10.3373C2.78623 10.1453 2.62912 9.98822 2.43711 9.98822ZM2.08799 13.9332H0.831169V10.6865H2.08799V13.9332Z" fill="#1C0732"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="15" height="15" fill="white" transform="translate(0 0.572266)"/>
</clipPath>
</defs>
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.97229 9.33594H7.03111C6.78499 9.33594 6.58548 9.53545 6.58548 9.78157V15.5525C6.58548 15.7986 6.78499 15.9982 7.03111 15.9982H9.97229C10.2184 15.9982 10.4179 15.7986 10.4179 15.5525V9.78157C10.4179 9.53545 10.2184 9.33594 9.97229 9.33594ZM9.52665 15.1069H7.47675V10.2272H9.52665V15.1069Z" fill="#696F8C"/>
<path d="M15.8101 5.83594H12.8466C12.6004 5.83594 12.4009 6.03545 12.4009 6.28157V15.5508C12.4009 15.7969 12.6004 15.9964 12.8466 15.9964H15.8101C16.0562 15.9964 16.2557 15.7969 16.2557 15.5508V6.28157C16.2557 6.03549 16.0562 5.83594 15.8101 5.83594ZM15.3644 15.1051H13.2922V6.72721H15.3644V15.1051Z" fill="#696F8C"/>
<path d="M4.15725 11.5H1.19377C0.947648 11.5 0.748138 11.6995 0.748138 11.9456V15.5553C0.748138 15.8014 0.947648 16.0009 1.19377 16.0009H4.15725C4.40337 16.0009 4.60288 15.8014 4.60288 15.5553V11.9456C4.60288 11.6996 4.40337 11.5 4.15725 11.5ZM3.71161 15.1096H1.63941V12.3913H3.71161V15.1096Z" fill="#696F8C"/>
<path d="M16.2995 0.446908C16.2317 0.353508 16.1251 0.296103 16.0098 0.290942L11.9545 0.00127327C11.7084 -0.0171936 11.4939 0.167389 11.4754 0.413471C11.457 0.659596 11.6415 0.874076 11.8876 0.892542L14.7578 1.08745L9.48123 5.21523L5.44826 2.05122C5.27604 1.91483 5.03017 1.9243 4.86892 2.07348L0.724546 6.03958C0.548365 6.20672 0.538471 6.48419 0.702284 6.66349C0.781781 6.76665 0.9064 6.82483 1.03652 6.81945C1.15406 6.81779 1.26618 6.76972 1.34845 6.68575L5.2032 2.98697L9.1916 6.12867C9.35439 6.25901 9.58585 6.25901 9.74864 6.12867L15.4528 1.6899L15.2299 4.45762C15.2288 4.69961 15.4124 4.90249 15.6533 4.92552H15.6755C15.9053 4.92667 16.0982 4.753 16.1212 4.52445L16.4108 0.758839C16.4272 0.642878 16.3856 0.526277 16.2995 0.446908Z" fill="#696F8C"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 6.5V9.5C9 9.76522 8.89464 10.0196 8.70711 10.2071C8.51957 10.3946 8.26522 10.5 8 10.5H2.5C2.23478 10.5 1.98043 10.3946 1.79289 10.2071C1.60536 10.0196 1.5 9.76522 1.5 9.5V4C1.5 3.73478 1.60536 3.48043 1.79289 3.29289C1.98043 3.10536 2.23478 3 2.5 3H5.5" stroke="#5B44BA" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 1.5H10.5V4.5" stroke="#5B44BA" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 7L10.5 1.5" stroke="#5B44BA" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 619 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 127 KiB

View File

@ -0,0 +1,14 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M17.4455 4.85906L12.7581 0.17168C12.6487 0.0622266 12.4994 0 12.3438 0H4.14062C3.17137 0 2.38281 0.788555 2.38281 1.75781V18.2422C2.38281 19.2114 3.17137 20 4.14062 20H15.8594C16.8286 20 17.6172 19.2114 17.6172 18.2422V5.27344C17.6172 5.11367 17.5501 4.96363 17.4455 4.85906ZM12.9297 2.00051L15.6167 4.6875H13.5156C13.1925 4.6875 12.9297 4.42465 12.9297 4.10156V2.00051ZM15.8594 18.8281H4.14062C3.81754 18.8281 3.55469 18.5653 3.55469 18.2422V1.75781C3.55469 1.43473 3.81754 1.17188 4.14062 1.17188H11.7578V4.10156C11.7578 5.07082 12.5464 5.85938 13.5156 5.85938H16.4453V18.2422C16.4453 18.5653 16.1825 18.8281 15.8594 18.8281Z" fill="#5B44BA"/>
<path d="M13.5156 8.28125H6.48438C6.16078 8.28125 5.89844 8.54359 5.89844 8.86719C5.89844 9.19078 6.16078 9.45312 6.48438 9.45312H13.5156C13.8392 9.45312 14.1016 9.19078 14.1016 8.86719C14.1016 8.54359 13.8392 8.28125 13.5156 8.28125Z" fill="#5B44BA"/>
<path d="M13.5156 10.625H6.48438C6.16078 10.625 5.89844 10.8873 5.89844 11.2109C5.89844 11.5345 6.16078 11.7969 6.48438 11.7969H13.5156C13.8392 11.7969 14.1016 11.5345 14.1016 11.2109C14.1016 10.8873 13.8392 10.625 13.5156 10.625Z" fill="#5B44BA"/>
<path d="M13.5156 12.9688H6.48438C6.16078 12.9688 5.89844 13.2311 5.89844 13.5547C5.89844 13.8783 6.16078 14.1406 6.48438 14.1406H13.5156C13.8392 14.1406 14.1016 13.8783 14.1016 13.5547C14.1016 13.2311 13.8392 12.9688 13.5156 12.9688Z" fill="#5B44BA"/>
<path d="M11.1719 15.3125H6.48438C6.16078 15.3125 5.89844 15.5748 5.89844 15.8984C5.89844 16.222 6.16078 16.4844 6.48438 16.4844H11.1719C11.4955 16.4844 11.7578 16.222 11.7578 15.8984C11.7578 15.5748 11.4955 15.3125 11.1719 15.3125Z" fill="#5B44BA"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 9.5H1V10V14V14.5H1.5H5.33219H5.83219V14V10V9.5H5.33219H1.5ZM8.80333 4.0541L8.47194 4.4L8.80333 4.7459L10.9711 7.00864L11.3322 7.3855L11.6932 7.00864L13.861 4.7459L14.1924 4.4L13.861 4.0541L11.6932 1.79136L11.3322 1.4145L10.9711 1.79136L8.80333 4.0541ZM3.41609 6.5C4.77045 6.5 5.83219 5.3603 5.83219 4C5.83219 2.6397 4.77045 1.5 3.41609 1.5C2.06174 1.5 1 2.6397 1 4C1 5.3603 2.06174 6.5 3.41609 6.5ZM11.0805 14.5C12.4348 14.5 13.4966 13.3603 13.4966 12C13.4966 10.6397 12.4348 9.5 11.0805 9.5C9.72612 9.5 8.66438 10.6397 8.66438 12C8.66438 13.3603 9.72612 14.5 11.0805 14.5Z" stroke="#696F8C"/>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@ -1,6 +1,3 @@
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 2.5C5 3.88071 3.88071 5 2.5 5C1.11929 5 0 3.88071 0 2.5C0 1.11929 1.11929 0 2.5 0C3.88071 0 5 1.11929 5 2.5Z" fill="#1C0732"/>
<path d="M15 12.5C15 13.8807 13.8807 15 12.5 15C11.1193 15 10 13.8807 10 12.5C10 11.1193 11.1193 10 12.5 10C13.8807 10 15 11.1193 15 12.5Z" fill="#1C0732"/>
<path d="M0 10H5V15H0V10Z" fill="#1C0732"/>
<path d="M10 3L12.8284 0.171573L15.6569 3L12.8284 5.82843L10 3Z" fill="#1C0732"/>
<svg width="19" height="20" viewBox="0 0 19 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 12.1784H1V12.6784V18.0117V18.5117H1.5H6.60958H7.10958V18.0117V12.6784V12.1784H6.60958H1.5ZM11.3581 4.86582L11.0267 5.21172L11.3581 5.55762L14.2485 8.57461L14.6096 8.95146L14.9706 8.57461L17.861 5.55762L18.1924 5.21172L17.861 4.86582L14.9706 1.84883L14.6096 1.47198L14.2485 1.84883L11.3581 4.86582ZM4.05479 7.84505C5.76189 7.84505 7.10958 6.40688 7.10958 4.67839C7.10958 2.9499 5.76189 1.51172 4.05479 1.51172C2.3477 1.51172 1 2.9499 1 4.67839C1 6.40688 2.3477 7.84505 4.05479 7.84505ZM14.274 18.5117C15.9811 18.5117 17.3287 17.0735 17.3287 15.3451C17.3287 13.6166 15.9811 12.1784 14.274 12.1784C12.5669 12.1784 11.2192 13.6166 11.2192 15.3451C11.2192 17.0735 12.5669 18.5117 14.274 18.5117Z" stroke="#1C0732"/>
</svg>

Before

Width:  |  Height:  |  Size: 525 B

After

Width:  |  Height:  |  Size: 828 B

View File

@ -7,6 +7,7 @@ import {
QueryLabelValue,
} from '../models/dashboardsData';
import {
metricsTimeStampValue,
PrometheusResponse,
promQueryInput,
} from '../models/graphql/prometheus';
@ -69,7 +70,8 @@ export const DataParserForPrometheus = (
prometheusData: PrometheusResponse,
lineGraph: string[],
areaGraph: string[],
closedAreaQueryIDs: string[]
closedAreaQueryIDs: string[],
selectedApplications?: string[]
) => {
const parsedPrometheusData: ParsedPrometheusData = {
seriesData: [],
@ -98,11 +100,28 @@ export const DataParserForPrometheus = (
prometheusData.GetPromQuery.metricsResponse?.forEach(
(queryResponse, mainIndex) => {
if (queryResponse && queryResponse.legends && queryResponse.tsvs) {
let { legends } = queryResponse;
let { tsvs } = queryResponse;
if (selectedApplications && selectedApplications.length) {
const newLegends: string[] = [];
const newTsvs: metricsTimeStampValue[][] = [];
queryResponse.legends.forEach((legend, index) => {
const filteredApps: string[] = selectedApplications.filter((app) =>
legend.includes(app)
);
if (filteredApps.length) {
newLegends.push(legend);
newTsvs.push(queryResponse.tsvs[index]);
}
});
legends = newLegends;
tsvs = newTsvs;
}
if (closedAreaQueryIDs.includes(queryResponse.queryid)) {
parsedPrometheusData.closedAreaData.push(
...queryResponse.legends.map((elem, index) => ({
...legends.map((elem, index) => ({
metricName: elem,
data: queryResponse.tsvs[index].map((dataPoint) => ({
data: tsvs[index].map((dataPoint) => ({
...dataPoint,
})),
baseColor:
@ -113,9 +132,9 @@ export const DataParserForPrometheus = (
);
} else {
parsedPrometheusData.seriesData.push(
...queryResponse.legends.map((elem, index) => ({
...legends.map((elem, index) => ({
metricName: elem,
data: queryResponse.tsvs[index].map((dataPoint) => ({
data: tsvs[index].map((dataPoint) => ({
...dataPoint,
})),
baseColor:

View File

@ -4,7 +4,7 @@ import AccordionDetails from '@material-ui/core/AccordionDetails';
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import ArrowDropUpIcon from '@material-ui/icons/ArrowDropUp';
import { ButtonFilled } from 'litmus-ui';
import { TextButton } from 'litmus-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ChaosEventDetails } from '../../../../models/dashboardsData';
@ -78,11 +78,10 @@ const ChaosAccordion: React.FC<ChaosAccordionProps> = ({
className={classes.accordionSummary}
key={`chaos-table-${dashboardKey}`}
>
<ButtonFilled
<TextButton
className={classes.button}
onClick={() => {
setChaosTableOpen(!chaosTableOpen);
}}
onClick={() => setChaosTableOpen(!chaosTableOpen)}
variant="highlight"
startIcon={
!chaosTableOpen ? (
<ArrowDropDownIcon className={classes.tableDropIcon} />
@ -93,14 +92,14 @@ const ChaosAccordion: React.FC<ChaosAccordionProps> = ({
>
<Typography className={classes.chaosHelperText}>
{!chaosTableOpen
? `${t(
? t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.showTable'
)}`
: `${t(
)
: t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.hideTable'
)}`}
)}
</Typography>
</ButtonFilled>
</TextButton>
<IconButton
aria-label="edit chaos query"
aria-haspopup="true"
@ -114,9 +113,9 @@ const ChaosAccordion: React.FC<ChaosAccordionProps> = ({
<StyledAccordionDetails className={classes.accordionDetails}>
<ChaosTable
chaosList={chaosEventsToBeShown}
selectEvents={(selectedEvents: string[]) => {
postEventSelectionRoutine(selectedEvents);
}}
selectEvents={(selectedEvents: string[]) =>
postEventSelectionRoutine(selectedEvents)
}
/>
</StyledAccordionDetails>
</Accordion>

View File

@ -4,7 +4,8 @@ const useStyles = makeStyles((theme) => ({
accordionSummary: {
display: 'flex',
justifyItems: 'center',
background: theme.palette.disabledBackground,
background: theme.palette.cards.header,
borderRadius: '3px 3px 0 0',
},
accordionDetails: {

View File

@ -31,17 +31,21 @@ const TableData: React.FC<TableDataProps> = ({
/>
</StyledTableCell>
<StyledTableCell>
<div
className={classes.colorBar}
style={{
background: data.legend,
}}
/>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '15rem' }}
>
<div
className={classes.colorCircle}
style={{ background: data.legendColor }}
/>
{data.chaosResultName}
</Typography>
</StyledTableCell>
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '25rem' }}
style={{ maxWidth: '12.5rem' }}
>
{data.workflow}
</Typography>
@ -49,15 +53,15 @@ const TableData: React.FC<TableDataProps> = ({
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '10rem' }}
style={{ maxWidth: '7.5rem' }}
>
{data.experiment}
{data.engineContext}
</Typography>
</StyledTableCell>
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '15rem' }}
style={{ maxWidth: '7.5rem' }}
>
{data.target}
</Typography>
@ -65,14 +69,14 @@ const TableData: React.FC<TableDataProps> = ({
<StyledTableCell>
<Typography
className={`${classes.tableObjects} ${
data.result === CHAOS_EXPERIMENT_VERDICT_PASS
data.verdict === CHAOS_EXPERIMENT_VERDICT_PASS
? classes.pass
: data.result === CHAOS_EXPERIMENT_VERDICT_FAIL
: data.verdict === CHAOS_EXPERIMENT_VERDICT_FAIL
? classes.fail
: classes.awaited
}`}
>
{data.result}
{data.verdict}
</Typography>
</StyledTableCell>
</>

View File

@ -32,53 +32,42 @@ const TableHeader: React.FC<TableHeaderProps> = ({
/>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead1'
)}
</div>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead1'
)}
</div>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead2'
)}
</div>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead2'
)}
</div>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead3'
)}
</div>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead3'
)}
</div>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead4'
)}
</div>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead4'
)}
</div>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead5.title'
)}
</div>
<div className={classes.nameHead}>
{t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead5.title'
)}
<InfoTooltip
value={t(
'analyticsDashboard.monitoringDashboardPage.chaosTable.tableHead5.infoText'
)}
className={classes.infoIcon}
/>
</div>
</StyledTableCell>

View File

@ -77,7 +77,11 @@ const ChaosTable: React.FC<ChaosTableProps> = ({ chaosList, selectEvents }) => {
<div>
<section className="table section">
<Paper className={classes.tableBody}>
<TableContainer className={classes.tableMain}>
<TableContainer
className={`${classes.tableMain} ${
!chaosList.length ? classes.empty : ''
}`}
>
<Table aria-label="simple table">
<TableHeader
onSelectAllClick={handleSelectAllClick}
@ -120,9 +124,9 @@ const ChaosTable: React.FC<ChaosTableProps> = ({ chaosList, selectEvents }) => {
<TableCell colSpan={6}>
<div className={classes.noRecords}>
<img
src="/icons/cloudIcon.svg"
src="/icons/dashboardUnavailable.svg"
className={classes.cloudIcon}
alt="Chaos cloud"
alt="Chaos event unavailable"
/>
<Typography
align="center"
@ -153,6 +157,21 @@ const ChaosTable: React.FC<ChaosTableProps> = ({ chaosList, selectEvents }) => {
page={page}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
SelectProps={{
MenuProps: {
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
},
}}
classes={{ menuItem: classes.menuListItem }}
/>
)}
</Paper>

View File

@ -6,10 +6,13 @@ const useStyles = makeStyles((theme) => ({
width: '100%',
flexDirection: 'column',
overflow: 'hidden',
boxShadow:
'0 0.3px 0.9px rgba(0, 0, 0, 0.1), 0 1.6px 3.6px rgba(0, 0, 0, 0.13)',
borderRadius: '3px 3px 0 0',
},
tableMain: {
background: theme.palette.cards.header,
background: theme.palette.background.paper,
maxHeight: '30rem',
'&::-webkit-scrollbar': {
width: '0.2em',
@ -28,42 +31,47 @@ const useStyles = makeStyles((theme) => ({
},
},
empty: {
'& td': {
borderBottom: 0,
},
},
tableBody: {
background: theme.palette.cards.header,
background: theme.palette.background.paper,
},
tableHead: {
background: theme.palette.cards.header,
background: theme.palette.background.paper,
},
nameHead: {
display: 'flex',
color: theme.palette.text.hint,
margin: theme.spacing(2, 0, 1.5),
fontSize: '0.875rem',
fontSize: '0.75rem',
lineHeight: '150%',
fontWeight: 500,
letterSpacing: '0.02em',
margin: theme.spacing(1, 0),
},
tableObjects: {
textAlign: 'center',
paddingLeft: theme.spacing(1),
display: 'flex',
gap: '0.5rem',
textAlign: 'left',
color: theme.palette.text.primary,
height: '1.75rem',
marginTop: theme.spacing(2),
fontSize: '0.875rem',
lineHeight: '130%',
fontSize: '0.75rem',
letterSpacing: '0.02em',
lineHeight: '150%',
paddingLeft: theme.spacing(0.5),
margin: theme.spacing(1, 0),
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
headSpacing: {
paddingLeft: theme.spacing(2.5),
},
nameContent: {
color: theme.palette.text.primary,
display: 'flex',
fontSize: '0.8rem',
justifyContent: 'center',
minWidth: '5rem',
paddingLeft: theme.spacing(2),
},
checkbox: {
@ -103,14 +111,28 @@ const useStyles = makeStyles((theme) => ({
marginRight: theme.spacing(1),
},
colorBar: {
height: '0.45rem',
width: '2.75rem',
margin: theme.spacing(0.75, 0, 0, 1.5),
colorCircle: {
height: '0.5rem',
width: '0.5rem',
borderRadius: '50%',
marginTop: theme.spacing(0.75),
},
infoIcon: {
margin: theme.spacing(1.75, 0, 0, 0.5),
// select
menuList: {
boxShadow: '0 5px 9px rgba(0, 0, 0, 0.1)',
},
menuListItem: {
background: `${theme.palette.background.paper} !important`,
fontSize: '0.875rem',
lineHeight: '150%',
height: '1.875rem',
'&:hover': {
background: `${theme.palette.cards.highlight} !important`,
},
'&.Mui-selected': {
background: `${theme.palette.cards.highlight} !important`,
},
},
}));

View File

@ -2,7 +2,7 @@
import { useMutation } from '@apollo/client';
import { Snackbar, Typography } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { ButtonFilled, ButtonOutlined, InputField, Modal } from 'litmus-ui';
import { ButtonFilled, InputField, Modal, TextButton } from 'litmus-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { v4 as uuidv4 } from 'uuid';
@ -160,24 +160,7 @@ const DashboardCloneModal: React.FC<DashboardCloneModalProps> = ({
return (
<div>
<Modal
open
onClose={() => {
onClose();
}}
modalActions={
<ButtonOutlined
className={classes.closeButton}
onClick={() => {
onClose();
}}
>
&#x2715;
</ButtonOutlined>
}
width="45%"
height="fit-content"
>
<Modal open onClose={() => onClose()} width="45%" height="fit-content">
<div className={classes.modal}>
<Typography className={classes.modalHeading} align="left">
{t(
@ -195,10 +178,8 @@ const DashboardCloneModal: React.FC<DashboardCloneModalProps> = ({
value={cloneName}
/>
<div className={classes.flexButtons}>
<ButtonOutlined
onClick={() => {
onClose();
}}
<TextButton
onClick={() => onClose()}
className={classes.cancelButton}
>
<Typography className={classes.buttonText}>
@ -206,7 +187,7 @@ const DashboardCloneModal: React.FC<DashboardCloneModalProps> = ({
'analyticsDashboard.monitoringDashboardPage.dashboardCloneModal.cancel'
)}
</Typography>
</ButtonOutlined>
</TextButton>
<ButtonFilled onClick={() => handleCreateMutation()}>
<Typography
className={`${classes.buttonText} ${classes.okButtonText}`}

View File

@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
modalHeading: {
margin: theme.spacing(6.5, 0, 7.5),
margin: theme.spacing(2.5, 0, 4.5),
paddingLeft: theme.spacing(6.5),
fontSize: '1.5rem',
},
@ -17,13 +17,6 @@ const useStyles = makeStyles((theme) => ({
padding: theme.spacing(5, 0),
},
closeButton: {
borderColor: theme.palette.border.main,
color: theme.palette.border.main,
padding: theme.spacing(0.5),
minWidth: '2.5rem',
},
buttonText: {
lineHeight: '140%',
fontSize: '0.875rem',
@ -35,7 +28,6 @@ const useStyles = makeStyles((theme) => ({
},
cancelButton: {
borderColor: theme.palette.border.main,
marginRight: theme.spacing(1.5),
padding: theme.spacing(0, 3),
},

View File

@ -30,19 +30,7 @@ const DataSourceInactiveModal: React.FC<DataSourceInactiveModalProps> = ({
return (
<Modal
open
onClose={() => {
history.goBack();
}}
modalActions={
<ButtonOutlined
className={classes.closeButton}
onClick={() => {
history.goBack();
}}
>
&#x2715;
</ButtonOutlined>
}
onClose={() => history.goBack()}
width="60%"
height="fit-content"
>
@ -83,12 +71,12 @@ const DataSourceInactiveModal: React.FC<DataSourceInactiveModalProps> = ({
{t('analyticsDashboard.monitoringDashboardPage.or')}
</Typography>
<ButtonOutlined
onClick={() => {
onClick={() =>
history.push({
pathname: '/analytics/datasource/configure',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
})
}
>
<img
src="/icons/disconnected.svg"

View File

@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
modalHeading: {
margin: theme.spacing(3.5, 0, 2.5),
margin: theme.spacing(2.5, 0),
paddingLeft: theme.spacing(6.5),
fontSize: '1.5rem',
},
@ -30,10 +30,6 @@ const useStyles = makeStyles((theme) => ({
padding: theme.spacing(5, 0),
},
closeButton: {
borderColor: theme.palette.border.main,
},
buttonIcon: {
padding: theme.spacing(0, 1.5, 0, 0.5),
},

View File

@ -1,5 +1,5 @@
import { FormControlLabel, FormGroup, Typography } from '@material-ui/core';
import Icon from '@material-ui/core/Icon';
import { FormControlLabel, Typography } from '@material-ui/core';
import { TextButton } from 'litmus-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { CheckBox } from '../../../../components/CheckBox';
@ -7,12 +7,18 @@ import {
DashboardConfigurationDetails,
PanelNameAndID,
} from '../../../../models/dashboardsData';
import useStyles from './styles';
import {
ApplicationMetadata,
Resource,
} from '../../../../models/graphql/dashboardsDetails';
import { ReactComponent as ExternalLinkIcon } from '../../../../svg/externalLink.svg';
import { ReactComponent as PrometheusIcon } from '../../../../svg/prometheus.svg';
import useStyles, { FormGroupApplicationsGrid, FormGroupGrid } from './styles';
interface InfoDropdownProps {
dashboardConfigurationDetails: DashboardConfigurationDetails;
metricsToBeShown: PanelNameAndID[];
applicationsToBeShown: string[];
applicationsToBeShown: ApplicationMetadata[];
postPanelSelectionRoutine: (selectedPanelList: string[]) => void;
postApplicationSelectionRoutine: (selectedApplicationList: string[]) => void;
}
@ -29,7 +35,7 @@ const InfoDropdown: React.FC<InfoDropdownProps> = ({
const [selectedApplications, setSelectedApplications] = React.useState<
string[]
>(applicationsToBeShown);
>([]);
const [selectedMetrics, setSelectedMetrics] = React.useState<string[]>(
metricsToBeShown.map((metric) => metric.id)
@ -88,7 +94,7 @@ const InfoDropdown: React.FC<InfoDropdownProps> = ({
className={classes.inlineIcon}
/>
<Typography className={classes.infoValue}>
{dashboardConfigurationDetails.typeID}
{dashboardConfigurationDetails.typeName}
</Typography>
</div>
</div>
@ -98,27 +104,19 @@ const InfoDropdown: React.FC<InfoDropdownProps> = ({
'analyticsDashboard.monitoringDashboardPage.infoDropdown.metaData3'
)}
</Typography>
<div className={classes.iconWithTextDiv}>
<img
src="/icons/prometheus.svg"
alt="data source Icon"
className={classes.inlineIcon}
/>
<TextButton
className={classes.button}
onClick={() =>
window.open(dashboardConfigurationDetails.dataSourceURL)
}
startIcon={<PrometheusIcon className={classes.inlineIcon} />}
endIcon={<ExternalLinkIcon className={classes.inlineIcon} />}
classes={{ label: classes.buttonLabel }}
>
<Typography className={classes.infoValue}>
{dashboardConfigurationDetails.dataSourceName}
</Typography>
<Icon
onClick={() => {
window.open(dashboardConfigurationDetails.dataSourceURL);
}}
>
<img
src="/icons/externalLink.svg"
alt="external link"
className={classes.linkIcon}
/>
</Icon>
</div>
</TextButton>
</div>
<div className={classes.dashboardMetaDataItem}>
<Typography className={classes.infoKey}>
@ -137,27 +135,47 @@ const InfoDropdown: React.FC<InfoDropdownProps> = ({
'analyticsDashboard.monitoringDashboardPage.infoDropdown.subHeading2'
)}
</Typography>
<div className={classes.checkBoxesContainer}>
<FormGroup key="application-group">
{applicationsToBeShown.map((application: string) => (
<FormControlLabel
control={
<CheckBox
checked={selectedApplications.includes(application)}
onChange={() => handleApplicationSelect(application)}
name={application}
/>
}
label={
<Typography className={classes.formControlLabel}>
{application}
<FormGroupApplicationsGrid key="application-group">
{applicationsToBeShown?.map(
(applicationMetadata: ApplicationMetadata) => (
<div key={`${applicationMetadata.namespace}-namespace`}>
<div className={classes.namespaceBox}>
<Typography className={classes.infoKey}>
{t(
'analyticsDashboard.monitoringDashboardPage.infoDropdown.infoKeyNamespace'
)}
</Typography>
}
key={`${application}-application-label`}
/>
))}
</FormGroup>
</div>
<Typography className={classes.infoValue}>
{applicationMetadata.namespace}
</Typography>
</div>
{applicationMetadata.applications.map(
(resource: Resource) => (
<div key={`${resource.kind}-resource`}>
{resource.names.map((name: string) => (
<FormControlLabel
control={
<CheckBox
checked={selectedApplications.includes(name)}
onChange={() => handleApplicationSelect(name)}
name={name}
/>
}
label={
<Typography className={classes.formControlLabel}>
{`${resource.kind} / ${name}`}
</Typography>
}
key={`${resource.kind} / ${name}-application-label`}
/>
))}
</div>
)
)}
</div>
)
)}
</FormGroupApplicationsGrid>
</div>
<div className={classes.infoSectionElement}>
<Typography className={classes.sectionHeader}>
@ -165,27 +183,25 @@ const InfoDropdown: React.FC<InfoDropdownProps> = ({
'analyticsDashboard.monitoringDashboardPage.infoDropdown.subHeading3'
)}
</Typography>
<div className={classes.checkBoxesContainer}>
<FormGroup key="metric-group">
{metricsToBeShown.map((metric: PanelNameAndID) => (
<FormControlLabel
control={
<CheckBox
checked={selectedMetrics.includes(metric.id)}
onChange={() => handleMetricSelect(metric.id)}
name={metric.name}
/>
}
label={
<Typography className={classes.formControlLabel}>
{metric.name}
</Typography>
}
key={`${metric}-metric-label`}
/>
))}
</FormGroup>
</div>
<FormGroupGrid key="metric-group">
{metricsToBeShown.map((metric: PanelNameAndID) => (
<FormControlLabel
control={
<CheckBox
checked={selectedMetrics.includes(metric.id)}
onChange={() => handleMetricSelect(metric.id)}
name={metric.name}
/>
}
label={
<Typography className={classes.formControlLabel}>
{metric.name}
</Typography>
}
key={`${metric}-metric-label`}
/>
))}
</FormGroupGrid>
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
import { makeStyles } from '@material-ui/core';
import { createStyles, FormGroup, makeStyles } from '@material-ui/core';
import { withStyles } from '@material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
@ -33,6 +34,11 @@ const useStyles = makeStyles((theme) => ({
padding: theme.spacing(1, 0, 3),
letterSpacing: '0.1714px',
},
namespaceBox: {
display: 'flex',
gap: '1rem',
margin: theme.spacing(0.5, 0, 1),
},
dashboardMetaDataItem: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
@ -40,19 +46,13 @@ const useStyles = makeStyles((theme) => ({
},
infoKey: {
fontSize: '0.825rem',
lineHeight: '0.9375rem',
lineHeight: '150%',
color: theme.palette.highlight,
},
infoValue: {
fontSize: '0.825rem',
lineHeight: '150%',
},
checkBoxesContainer: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
maxHeight: '7rem',
overflowY: 'scroll',
paddingLeft: theme.spacing(1),
color: theme.palette.text.primary,
},
formControlLabel: {
fontSize: '0.75rem',
@ -60,18 +60,50 @@ const useStyles = makeStyles((theme) => ({
lineHeight: '150%',
},
inlineIcon: {
margin: theme.spacing(0.5, 1, 0, 0),
width: '1rem',
height: '1rem',
},
linkIcon: {
margin: theme.spacing(0, 0, 0.45, 0.75),
margin: theme.spacing(0.25, 0),
width: '1rem',
height: '1rem',
},
iconWithTextDiv: {
display: 'flex',
gap: '0.5rem',
},
button: {
minWidth: 0,
minHeight: 0,
padding: 0,
width: 'fit-content',
'&:hover': {
cursor: 'pointer !important',
},
},
buttonLabel: {
justifyContent: 'flex-start',
marginLeft: theme.spacing(0.5),
},
}));
export const FormGroupGrid = withStyles((theme) =>
createStyles({
root: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
maxHeight: '7rem',
overflowY: 'auto',
paddingLeft: theme.spacing(0.5),
},
})
)(FormGroup);
export const FormGroupApplicationsGrid = withStyles((theme) =>
createStyles({
root: {
display: 'grid',
maxHeight: '7rem',
overflowY: 'auto',
paddingLeft: theme.spacing(0.5),
},
})
)(FormGroup);
export default useStyles;

View File

@ -57,6 +57,7 @@ const PanelContent: React.FC<GraphPanelProps> = ({
unit,
className,
controllerPanelID,
selectedApplications,
}) => {
const { palette } = useTheme();
const classes = useStyles();
@ -120,7 +121,8 @@ const PanelContent: React.FC<GraphPanelProps> = ({
areaGraph,
prom_queries
.filter((query) => query.close_area)
.map((query) => query.queryid)
.map((query) => query.queryid),
selectedApplications
);
setGraphData(parsedData);
}

View File

@ -14,6 +14,7 @@ interface DashboardPanelGroupContentProps {
panel_group_name: string;
panel_group_id: string;
selectedPanels?: string[];
selectedApplications?: string[];
}
const DashboardPanelGroupContent: React.FC<DashboardPanelGroupContentProps> = ({
@ -21,6 +22,7 @@ const DashboardPanelGroupContent: React.FC<DashboardPanelGroupContentProps> = ({
panel_group_id,
panel_group_name,
selectedPanels,
selectedApplications,
}) => {
const classes = useStyles();
const [open, setOpen] = React.useState<boolean>(true);
@ -63,6 +65,7 @@ const DashboardPanelGroupContent: React.FC<DashboardPanelGroupContentProps> = ({
x_axis_down={panel.x_axis_down}
unit={panel.unit}
controllerPanelID={selectedPanels ? selectedPanels[0] : ''}
selectedApplications={selectedApplications ?? []}
/>
))}
</AccordionDetails>

View File

@ -22,8 +22,8 @@ const useStyles = makeStyles((theme) => ({
alignContent: 'left',
background: theme.palette.cards.header,
boxShadow:
'0px 0.3px 0.9px rgba(0, 0, 0, 0.1), 0px 1.6px 3.6px rgba(0, 0, 0, 0.13)',
borderRadius: '3px 3px 0px 0px',
'0 0.3px 0.9px rgba(0, 0, 0, 0.1), 0 1.6px 3.6px rgba(0, 0, 0, 0.13)',
borderRadius: '3px 3px 0 0',
},
panelGroupContainer: {
@ -32,10 +32,10 @@ const useStyles = makeStyles((theme) => ({
background: theme.palette.background.paper,
display: 'grid',
gridTemplateColumns: '49% 49%',
gridGap: theme.spacing(1.75),
gridGap: '0.875rem',
padding: theme.spacing(1, 1, 1, 1.75),
boxShadow:
'0px 0.3px 0.9px rgba(0, 0, 0, 0.1), 0px 1.6px 3.6px rgba(0, 0, 0, 0.13)',
'0 0.3px 0.9px rgba(0, 0, 0, 0.1), 0 1.6px 3.6px rgba(0, 0, 0, 0.13)',
},
expand: {

View File

@ -127,7 +127,9 @@ const ToolBar: React.FC = () => {
<div className={classes.controls}>
<div ref={dateRangeSelectorRef}>
<ButtonOutlined
className={classes.selectDate}
className={`${classes.selectDate} ${
isDateRangeSelectorPopoverOpen ? classes.selectDateFocused : ''
}`}
onClick={() => setDateRangeSelectorPopoverOpen(true)}
aria-label="time range"
aria-haspopup="true"
@ -249,6 +251,7 @@ const ToolBar: React.FC = () => {
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
}}
>
<MenuItem
@ -256,23 +259,7 @@ const ToolBar: React.FC = () => {
value={MAX_REFRESH_RATE}
className={classes.menuListItem}
>
<Typography
className={classes.menuListItemText}
style={{
fontWeight: refreshRate === MAX_REFRESH_RATE ? 500 : 400,
}}
>
{t('analyticsDashboard.monitoringDashboardPage.refresh.off')}
</Typography>
{refreshRate === MAX_REFRESH_RATE ? (
<img
src="/icons/check.svg"
alt="check mark"
className={classes.checkIcon}
/>
) : (
<></>
)}
{t('analyticsDashboard.monitoringDashboardPage.refresh.off')}
</MenuItem>
{refreshData.map((data: RefreshObjectType) => (
<MenuItem
@ -280,23 +267,7 @@ const ToolBar: React.FC = () => {
value={data.value}
className={classes.menuListItem}
>
<Typography
className={classes.menuListItemText}
style={{
fontWeight: refreshRate === data.value ? 500 : 400,
}}
>
{t(data.label)}
</Typography>
{refreshRate === data.value ? (
<img
src="/icons/check.svg"
alt="check mark"
className={classes.checkIcon}
/>
) : (
<></>
)}
{t(data.label)}
</MenuItem>
))}
</Select>

View File

@ -29,6 +29,10 @@ const useStyles = makeStyles((theme) => ({
},
},
selectDateFocused: {
border: `2px solid ${theme.palette.highlight}`,
},
displayDate: {
width: '100%',
color: theme.palette.text.primary,
@ -50,18 +54,21 @@ const useStyles = makeStyles((theme) => ({
margin: theme.spacing(-2, 0, 0, -4.25),
},
// select
menuList: {
boxShadow: '0 5px 9px rgba(0, 0, 0, 0.1)',
},
menuListItem: {
background: `${theme.palette.background.paper} !important`,
fontSize: '0.875rem',
lineHeight: '150%',
height: '1.75rem',
width: '11.5rem',
display: 'flex',
justifyContent: 'space-between',
},
menuListItemText: {
fontSize: '0.875rem',
height: '1.875rem',
'&:hover': {
background: `${theme.palette.cards.highlight} !important`,
},
'&.Mui-selected': {
background: `${theme.palette.cards.highlight} !important`,
},
},
checkIcon: {
@ -132,10 +139,10 @@ export const useOutlinedInputStyles = makeStyles((theme: Theme) => ({
borderColor: theme.palette.border.main,
},
'&:hover $notchedOutline': {
borderColor: theme.palette.primary.main,
borderColor: theme.palette.highlight,
},
'&$focused $notchedOutline': {
borderColor: theme.palette.primary.main,
borderColor: theme.palette.highlight,
},
'& .MuiInputLabel-root': {
color: `${theme.palette.text.hint} !important`,

View File

@ -150,13 +150,14 @@ const TopNavButtons: React.FC<TopNavButtonsProps> = ({
};
return (
<div className={classes.button}>
<div className={classes.buttons}>
{navButtonStates.isInfoToggled ? (
<ButtonFilled
onClick={() => {
setNavButtonStates({ ...navButtonStates, isInfoToggled: false });
switchIsInfoToggled(false);
}}
className={classes.button}
>
<img
src="/icons/infoWhite.svg"
@ -173,6 +174,7 @@ const TopNavButtons: React.FC<TopNavButtonsProps> = ({
setNavButtonStates({ ...navButtonStates, isInfoToggled: true });
switchIsInfoToggled(true);
}}
className={classes.button}
>
<img src="/icons/info.svg" alt="Info icon" className={classes.icon} />
<Typography className={classes.infoText}>
@ -190,7 +192,7 @@ const TopNavButtons: React.FC<TopNavButtonsProps> = ({
isOptionsToggled: false,
});
}}
className={classes.optionsButton}
className={classes.button}
>
<img
src="/icons/menu-active.svg"
@ -207,7 +209,7 @@ const TopNavButtons: React.FC<TopNavButtonsProps> = ({
isOptionsToggled: true,
});
}}
className={classes.optionsButton}
className={classes.button}
>
<img
src="/icons/menu.svg"
@ -262,7 +264,7 @@ const TopNavButtons: React.FC<TopNavButtonsProps> = ({
data-cy="optionsConfigureDashboard"
className={classes.btnText}
>
{t('analyticsDashboardViews.kubernetesDashboard.table.configure')}
{t('analyticsDashboard.applicationDashboardTable.configure')}
</Typography>
</div>
</MenuItem>

View File

@ -5,10 +5,11 @@ const useStyles = makeStyles((theme) => ({
margin: theme.spacing(0, 1),
width: '1rem',
},
button: {
buttons: {
position: 'relative',
display: 'flex',
justifyContent: 'space-between',
gap: '1rem',
},
infoText: {
paddingRight: theme.spacing(1.5),
@ -17,17 +18,17 @@ const useStyles = makeStyles((theme) => ({
margin: theme.spacing(0, 1),
width: '1.25rem',
},
optionsButton: {
marginLeft: theme.spacing(2),
button: {
minWidth: 0,
padding: theme.spacing(1.5, 1),
},
// Menu option with icon
menuList: {
marginLeft: theme.spacing(-1),
marginLeft: theme.spacing(-3),
},
menuItem: {
width: '11.5rem',
width: '9.5rem',
height: '2.5rem',
},
expDiv: {

View File

@ -82,15 +82,11 @@ const DashboardCards: React.FC<DashboardCardsProps> = ({
{upload && (
<Modal
open
onClose={() => {
setUpload(false);
}}
onClose={() => setUpload(false)}
modalActions={
<ButtonOutlined
className={classes.closeButton}
onClick={() => {
setUpload(false);
}}
onClick={() => setUpload(false)}
>
&#x2715;
</ButtonOutlined>
@ -106,9 +102,7 @@ const DashboardCards: React.FC<DashboardCardsProps> = ({
</Typography>
<UploadJSON
successHandler={() => handleClick()}
errorHandler={() => {
generateAlert();
}}
errorHandler={() => generateAlert()}
/>
</div>
</Modal>

View File

@ -23,6 +23,12 @@ const useStyles = makeStyles((theme) => ({
'&:hover': {
border: `1px solid ${theme.palette.primary.main}`,
},
[theme.breakpoints.down('md')]: {
height: '9rem',
},
[theme.breakpoints.down('sm')]: {
height: 'fit-content',
},
},
// CARD MEDIA
@ -40,6 +46,7 @@ const useStyles = makeStyles((theme) => ({
// CARD CONTENT
cardContent: {
padding: theme.spacing(1.25, 1, 0),
display: 'grid',
gridTemplateColumns: '0.2fr 0.8fr',
},
@ -78,8 +85,8 @@ const useStyles = makeStyles((theme) => ({
closeButton: {
borderColor: theme.palette.border.main,
color: theme.palette.border.main,
padding: theme.spacing(0.5),
minWidth: '2.5rem',
padding: theme.spacing(0.25, 2),
minWidth: 0,
},
}));

View File

@ -66,14 +66,69 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
const selectedDashboard = useSelector(
(state: RootState) => state.selectDashboard
);
const [update, setUpdate] = useState(false);
const [availableApplicationMetadataMap, setAvailableApplicationMetadataMap] =
useState<ApplicationMetadata[]>([]);
const [kubeObjInput, setKubeObjInput] = useState<GVRRequest>({
group: '',
version: 'v1',
resource: 'pods',
});
const [selectedNamespaceList, setSelectedNamespaceList] = useState<
Array<Option>
>([]);
const [activeAgents, setActiveAgents] = useState<Cluster[]>([]);
const getSelectedApps = (dashboardJSON: any) => {
dashboard.selectDashboard({
selectedDashboardID: '',
});
return dashboardJSON.applicationMetadataMap;
const selectedApps: ApplicationMetadata[] = [];
dashboardJSON.applicationMetadataMap?.forEach(
(applicationMetadata: ApplicationMetadata) => {
const namespaceApps = availableApplicationMetadataMap.filter(
(appMeta) => appMeta.namespace === applicationMetadata.namespace
)[0];
applicationMetadata.applications.forEach((app) => {
const kindApps = namespaceApps.applications.filter(
(appKind) => appKind.kind === app.kind
)[0];
const availableApps = app.names.filter((name) =>
kindApps.names.includes(name)
);
if (availableApps.length) {
let nsIndex = -1;
selectedApps.forEach((existingApp, index) => {
if (existingApp.namespace === applicationMetadata.namespace) {
nsIndex = index;
}
});
if (nsIndex !== -1) {
selectedApps[nsIndex].applications.push({
kind: app.kind,
names: availableApps,
});
} else {
selectedApps.push({
namespace: applicationMetadata.namespace,
applications: [
{
kind: app.kind,
names: availableApps,
},
],
});
}
}
});
}
);
return selectedApps;
};
const [activeDataSources, setActiveDataSources] = useState<
ListDataSourceResponse[]
>([]);
const [dashboardDetails, setDashboardDetails] = useState<DashboardDetails>({
id: !configure ? '' : dashboardVars.id ?? '',
name: !configure
@ -114,29 +169,20 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
: dashboardVars.information ?? '',
panelGroupMap: dashboardVars.panelGroupMap ?? [],
panelGroups: dashboardVars.panelGroups ?? [],
applicationMetadataMap: !configure
? selectedDashboard.selectedDashboardID !== 'upload'
? dashboardVars.applicationMetadataMap
: getSelectedApps(selectedDashboard.dashboardJSON)
: dashboardVars.applicationMetadataMap ?? [],
applicationMetadataMap:
!configure && selectedDashboard.selectedDashboardID === 'upload'
? getSelectedApps(selectedDashboard.dashboardJSON)
: dashboardVars.applicationMetadataMap ?? [],
});
const [update, setUpdate] = useState(false);
const [availableApplicationMetadataMap, setAvailableApplicationMetadataMap] =
useState<ApplicationMetadata[]>([]);
const [kubeObjInput, setKubeObjInput] = useState<GVRRequest>({
group: '',
version: 'v1',
resource: 'pods',
});
const [selectedNamespaceList, setSelectedNamespaceList] = useState<
Array<Option>
>([]);
// Apollo query to get the agent data
const { data: agentList } = useQuery<Clusters, ClusterVars>(GET_CLUSTER, {
variables: { project_id: projectID },
fetchPolicy: 'cache-and-network',
});
const { data: agentList, loading } = useQuery<Clusters, ClusterVars>(
GET_CLUSTER,
{
variables: { project_id: projectID },
fetchPolicy: 'cache-and-network',
}
);
/**
* GraphQL subscription to fetch the KubeObjData from the server
@ -242,16 +288,15 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
}, [update]);
useEffect(() => {
if (dashboardDetails.agentID === '' && !configure) {
const availableAgents = (agentList?.getCluster ?? []).filter(
(cluster) => {
return (
cluster.is_active &&
cluster.is_cluster_confirmed &&
cluster.is_registered
);
}
const availableAgents = (agentList?.getCluster ?? []).filter((cluster) => {
return (
cluster.is_active &&
cluster.is_cluster_confirmed &&
cluster.is_registered
);
});
setActiveAgents(availableAgents);
if (dashboardDetails.agentID === '' && !configure) {
setDashboardDetails({
...dashboardDetails,
agentID: availableAgents.length ? availableAgents[0].cluster_id : '',
@ -261,10 +306,11 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
}, [agentList]);
useEffect(() => {
const availableDataSources = dataSourceList.filter((dataSource) => {
return dataSource.health_status === 'Active';
});
setActiveDataSources(availableDataSources);
if (dashboardDetails.dataSourceID === '' && !configure) {
const availableDataSources = dataSourceList.filter((dataSource) => {
return dataSource.health_status === 'Active';
});
setDashboardDetails({
...dashboardDetails,
dataSourceID: availableDataSources.length
@ -278,28 +324,41 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
}
}, [dataSourceList]);
useEffect(() => {
if (!configure && selectedDashboard.selectedDashboardID === 'upload') {
setDashboardDetails({
...dashboardDetails,
applicationMetadataMap: getSelectedApps(
selectedDashboard.dashboardJSON
),
});
}
}, [availableApplicationMetadataMap]);
const getAvailableApplications = () => {
const availableApplications: Array<Option> = [];
availableApplicationMetadataMap.forEach((appMetadata) => {
selectedNamespaceList.forEach((namespaceOption) => {
if (namespaceOption.name === appMetadata.namespace) {
const apps: Resource[] = appMetadata.applications.filter(
(application) => application.kind === kubeObjInput.resource
);
if (apps.length) {
apps[0].names.forEach((appName) => {
availableApplications.push({
name: `${
namespaceOption.name
} / ${kubeObjInput.resource.substring(
0,
kubeObjInput.resource.length - 1
)} / ${appName}`,
if (selectedNamespaceList.length) {
selectedNamespaceList.forEach((namespaceOption) => {
if (namespaceOption.name === appMetadata.namespace) {
const apps: Resource[] = appMetadata.applications.filter(
(application) => application.kind === kubeObjInput.resource
);
if (apps.length) {
apps[0].names.forEach((appName) => {
availableApplications.push({
name: `${
namespaceOption.name
} / ${kubeObjInput.resource.substring(
0,
kubeObjInput.resource.length - 1
)} / ${appName}`,
});
});
});
}
}
}
});
});
}
});
return availableApplications;
};
@ -393,21 +452,36 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.agent'
)}
className={classes.selectText}
disabled={activeAgents.length === 0}
>
{(agentList?.getCluster ?? [])
.filter((cluster) => {
return (
cluster.is_active &&
cluster.is_cluster_confirmed &&
cluster.is_registered
);
})
.map((agent: Cluster) => (
<MenuItem key={agent.cluster_id} value={agent.cluster_id}>
{agent.cluster_name}
</MenuItem>
))}
{activeAgents.map((agent: Cluster) => (
<MenuItem key={agent.cluster_id} value={agent.cluster_id}>
{agent.cluster_name}
</MenuItem>
))}
</Select>
{!activeAgents.length && !loading ? (
<Typography className={classes.formErrorText}>
{t(
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.noActiveAgent'
)}
</Typography>
) : !(agentList?.getCluster ?? []).filter((cluster) => {
return (
cluster.cluster_id === dashboardDetails.agentID &&
cluster.is_active &&
cluster.is_cluster_confirmed &&
cluster.is_registered
);
}).length ? (
<Typography className={classes.formErrorText}>
{t(
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.agentInactive'
)}
</Typography>
) : (
<></>
)}
</FormControl>
</div>
@ -436,13 +510,34 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.dataSource'
)}
className={classes.selectText}
disabled={activeDataSources.length === 0}
>
{dataSourceList.map((dataSource: ListDataSourceResponse) => (
{activeDataSources.map((dataSource: ListDataSourceResponse) => (
<MenuItem key={dataSource.ds_id} value={dataSource.ds_id}>
{dataSource.ds_name}
</MenuItem>
))}
</Select>
{!activeDataSources.length ? (
<Typography className={classes.formErrorText}>
{t(
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.noActiveDataSource'
)}
</Typography>
) : !dataSourceList.filter((dataSource) => {
return (
dataSource.health_status === 'Active' &&
dataSource.ds_id === dashboardDetails.dataSourceID
);
}).length ? (
<Typography className={classes.formErrorText}>
{t(
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.dataSourceInactive'
)}
</Typography>
) : (
<></>
)}
</FormControl>
<InputField
@ -472,12 +567,19 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
<AutocompleteChipInput
defaultValue={getSelectedAppNamespaces()}
onChange={(event, value, reason) => {
setSelectedNamespaceList(value as Array<Option>);
}}
options={availableApplicationMetadataMap.map((value) => {
return { name: value.namespace };
})}
onChange={(event, value) =>
setSelectedNamespaceList(value as Array<Option>)
}
getOptionSelected={(option) =>
selectedNamespaceList
.map((selections) => selections.name)
.includes(option.name)
}
options={
availableApplicationMetadataMap.map((value) => {
return { name: value.namespace };
}) ?? []
}
label={t(
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.selectNamespaces'
)}
@ -526,7 +628,7 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
<AutocompleteChipInput
defaultValue={getSelectedAppDetails()}
onChange={(event, value, reason) => {
onChange={(event, value) => {
const newSelection: ApplicationMetadata[] = [];
const selectedApps: Array<Option> = value as Array<Option>;
selectedApps.forEach((nsKindApp) => {
@ -575,6 +677,11 @@ const DashboardMetadataForm: React.FC<DashboardMetadataFormProps> = ({
});
setUpdate(true);
}}
getOptionSelected={(option) =>
getSelectedAppDetails()
.map((selections) => selections.name)
.includes(option.name)
}
options={getAvailableApplications()}
label={t(
'analyticsDashboard.applicationDashboards.configureDashboardMetadata.form.selectApplications'

View File

@ -41,6 +41,10 @@ const useStyles = makeStyles((theme) => ({
height: '3.25rem',
},
formErrorText: {
color: theme.palette.error.main,
},
namespaceSelect: {
width: '41.5rem',
margin: theme.spacing(3.5, 1),

View File

@ -128,7 +128,7 @@ const SelectTheMetricsForm: React.FC<SelectTheMetricsFormProps> = ({
{panel}
</Typography>
}
key={`metrics-group-${panelGroup.groupName}-label`}
key={`metric-${panel}-label`}
/>
))}
</FormGroup>

View File

@ -28,7 +28,7 @@ import ace from 'ace-builds';
background: rgba(31, 0, 230, 0.15)\
}\
.lp-code-bright.ace_multiselect .ace_selection.ace_start {\
box-shadow: 0 0 3px 0px #272822;\
box-shadow: 0 0 3px 0 #272822;\
}\
.lp-code-bright .ace_marker-layer .ace_step {\
background: rgb(102, 82, 0)\

View File

@ -355,7 +355,7 @@ const QueryEditor: React.FC<QueryEditorProps> = ({
<AutocompleteChipInput
value={selectedValuesForLabel}
onChange={(event, value, reason) => {
onChange={(event, value) => {
const selectedValues: Array<Option> = value as Array<Option>;
const existingLabelValuesList: QueryLabelValue[] =
localQuery.labels_and_values_list ?? [];
@ -386,6 +386,11 @@ const QueryEditor: React.FC<QueryEditorProps> = ({
getSelectedValuesForLabel(selectedLabel ?? '');
setUpdate(true);
}}
getOptionSelected={(option) =>
selectedValuesForLabel
.map((selections) => selections.name)
.includes(option.name)
}
options={getAvailableValues(selectedLabel ?? '')}
label={t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.values'
@ -466,8 +471,7 @@ const QueryEditor: React.FC<QueryEditorProps> = ({
</Typography>
<div
className={`${classes.flex} ${classes.paddedTop}`}
style={{ gap: '2.5rem', width: '98.5%', flexWrap: 'wrap' }}
className={`${classes.flex} ${classes.paddedTop} ${classes.configSection}`}
>
<div className={classes.flex}>
<InputField
@ -564,21 +568,10 @@ const QueryEditor: React.FC<QueryEditorProps> = ({
)}
</InputLabel>
<Select
value={
localQuery.line
? t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.lineGraph'
)
: t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.areaGraph'
)
}
value={localQuery.line ? 'Line graph' : 'Area graph'}
onChange={(event) => {
const line =
(event.target.value as string) ===
t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.areaGraph'
);
(event.target.value as string) === 'Line graph';
setLocalQuery({
...localQuery,
line,
@ -595,9 +588,7 @@ const QueryEditor: React.FC<QueryEditorProps> = ({
key={`${t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.lineGraph'
)}`}
value={t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.lineGraph'
)}
value="Line graph"
>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.lineGraph'
@ -607,9 +598,7 @@ const QueryEditor: React.FC<QueryEditorProps> = ({
key={`${t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.areaGraph'
)}`}
value={t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.areaGraph'
)}
value="Area graph"
>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.areaGraph'

View File

@ -82,6 +82,12 @@ const useStyles = makeStyles((theme) => ({
paddingTop: theme.spacing(3.5),
},
configSection: {
gap: '2.5rem',
width: '98.5%',
flexWrap: 'wrap',
},
paddedTop: {
paddingTop: theme.spacing(2.5),
},

View File

@ -9,7 +9,13 @@ import {
useTheme,
} from '@material-ui/core';
import { Autocomplete } from '@material-ui/lab';
import { ButtonFilled, ButtonOutlined, EditableText } from 'litmus-ui';
import {
ButtonFilled,
ButtonOutlined,
EditableText,
Modal,
TextButton,
} from 'litmus-ui';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -66,6 +72,10 @@ const QueryEditingWizard: React.FC<QueryEditingWizardProps> = ({
const theme = useTheme();
const { t } = useTranslation();
const [tabValue, setTabValue] = React.useState<number>(0);
const [discardChangesModalOpen, setDiscardChangesModalOpen] =
React.useState<boolean>(false);
const [deletePanelModalOpen, setDeletePanelModalOpen] =
React.useState<boolean>(false);
const [panelInfo, setPanelInfo] = useState<PanelDetails>({ ...panelVars });
const [update, setUpdate] = useState<Update>({
triggerUpdate: false,
@ -213,9 +223,7 @@ const QueryEditingWizard: React.FC<QueryEditingWizardProps> = ({
style={{ gap: '1rem' }}
>
<ButtonOutlined
onClick={() => {
setSettings(!settings);
}}
onClick={() => setSettings(!settings)}
className={classes.iconButton}
>
<img
@ -225,9 +233,7 @@ const QueryEditingWizard: React.FC<QueryEditingWizardProps> = ({
/>
</ButtonOutlined>
<ButtonOutlined
onClick={() => {
handleDeletePanel(index);
}}
onClick={() => setDeletePanelModalOpen(true)}
className={`${classes.iconButton} ${classes.deleteButton}`}
>
<img
@ -383,10 +389,8 @@ const QueryEditingWizard: React.FC<QueryEditingWizardProps> = ({
)}
<ButtonFilled
onClick={() => {
handleDiscardChanges(index);
}}
className={classes.discardButton}
onClick={() => setDiscardChangesModalOpen(true)}
variant="error"
>
<Typography>
{t(
@ -397,6 +401,116 @@ const QueryEditingWizard: React.FC<QueryEditingWizardProps> = ({
</div>
</>
)}
<Modal
open={deletePanelModalOpen}
onClose={() => setDeletePanelModalOpen(false)}
width="45%"
height="fit-content"
>
<div className={classes.modal}>
<Typography className={classes.modalHeading} align="left">
<b>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.removeMetric'
)}
</b>
</Typography>
<Typography className={classes.modalBodyText} align="left">
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.removeMetricConfirmation'
)}
<b>
<i>{` ${panelInfo.panel_name} `}</i>
</b>
{t('analyticsDashboard.applicationDashboards.tuneTheQueries.under')}
<b>
<i>{` ${panelInfo.panel_group_name} `}</i>
</b>
?
</Typography>
<div className={classes.flexButtons}>
<TextButton
onClick={() => setDeletePanelModalOpen(false)}
className={classes.cancelButton}
>
<Typography className={classes.buttonText}>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.cancel'
)}
</Typography>
</TextButton>
<ButtonFilled
onClick={() => handleDeletePanel(index)}
variant="error"
>
<Typography
className={`${classes.buttonText} ${classes.confirmButtonText}`}
>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.delete'
)}
</Typography>
</ButtonFilled>
</div>
</div>
</Modal>
<Modal
open={discardChangesModalOpen}
onClose={() => setDiscardChangesModalOpen(false)}
width="45%"
height="fit-content"
>
<div className={classes.modal}>
<Typography className={classes.modalHeading} align="left">
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.discardChangesConfirmation'
)}
<b>
<i>{` ${panelInfo.panel_name} `}</i>
</b>
{t('analyticsDashboard.applicationDashboards.tuneTheQueries.under')}
<b>
<i>{` ${panelInfo.panel_group_name} `}</i>
</b>
?
</Typography>
<Typography className={classes.modalBodyText} align="left">
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.discardChangesInfo'
)}
</Typography>
<div className={classes.flexButtons}>
<TextButton
onClick={() => setDiscardChangesModalOpen(false)}
className={classes.cancelButton}
>
<Typography className={classes.buttonText}>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.cancel'
)}
</Typography>
</TextButton>
<ButtonFilled
onClick={() => handleDiscardChanges(index)}
variant="error"
>
<Typography
className={`${classes.buttonText} ${classes.confirmButtonText}`}
>
{t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.yesProceed'
)}
</Typography>
</ButtonFilled>
</div>
</div>
</Modal>
</div>
);
};

View File

@ -38,13 +38,6 @@ const useStyles = makeStyles((theme) => ({
marginLeft: theme.spacing(1.5),
background: theme.palette.cards.header,
},
discardButton: {
background: theme.palette.error.main,
'&:hover': {
background: theme.palette.error.main,
cursor: 'pointer',
},
},
flexBetween: {
display: 'flex',
justifyContent: 'space-between',
@ -79,6 +72,40 @@ const useStyles = makeStyles((theme) => ({
header: {
padding: theme.spacing(1, 2, 4),
},
// modal
modalHeading: {
fontSize: '1.5rem',
lineHeight: '130%',
fontFeatureSettings: 'pnum on, lnum on',
margin: theme.spacing(2.5, 0, 4.5),
padding: theme.spacing(0, 6.5),
},
modalBodyText: {
fontSize: '1rem',
lineHeight: '130%',
padding: theme.spacing(0, 6.5),
},
flexButtons: {
display: 'flex',
justifyContent: 'flex-end',
padding: theme.spacing(5.5, 6.5, 0, 0),
},
modal: {
padding: theme.spacing(5, 0),
},
buttonText: {
lineHeight: '140%',
fontSize: '0.875rem',
},
confirmButtonText: {
color: theme.palette.text.secondary,
padding: theme.spacing(0, 3),
},
cancelButton: {
marginRight: theme.spacing(1.5),
padding: theme.spacing(0, 3),
},
}));
export default useStyles;

View File

@ -14,6 +14,7 @@ import {
PanelGroupDetails,
PromQueryDetails,
} from '../../../../../../models/dashboardsData';
import { PanelOption } from '../../../../../../models/graphql/dashboardsDetails';
import {
PrometheusSeriesListQueryVars,
PrometheusSeriesListResponse,
@ -205,9 +206,7 @@ const EditPanelsWizard: React.FC<EditPanelsWizardProps> = ({
setPanelGroupsList(newPanelGroupOptions);
};
const handleCreatePanel = () => {
const existingPanels: PanelDetails[] =
dashboardDetails.selectedPanels ?? [];
const getNewPanel = () => {
const newPanel: PanelDetails = {
panel_id: '',
panel_group_id: '',
@ -237,6 +236,14 @@ const EditPanelsWizard: React.FC<EditPanelsWizardProps> = ({
x_axis_down: '',
unit: '',
};
return newPanel;
};
const handleCreatePanel = () => {
const existingPanels: PanelDetails[] =
dashboardDetails.selectedPanels ?? [];
const newPanel = getNewPanel();
existingPanels.push(newPanel);
setDashboardDetails({
...dashboardDetails,
@ -353,9 +360,12 @@ const EditPanelsWizard: React.FC<EditPanelsWizardProps> = ({
dashboardDetails.selectedPanels ?? [];
let panelGroupList: PanelGroupDetails[] = [];
if (configure) {
panelGroupList = dashboardVars.panelGroups ?? [];
panelGroupList = dashboardVars.panelGroupMap ?? [];
} else {
panelGroupList = selectedDashboard.dashboardJSON.panelGroups;
panelGroupList =
dashboardVars.dashboardTypeID !== 'custom'
? selectedDashboard.dashboardJSON.panelGroups
: [];
}
panelGroupList.forEach((panelGroup) => {
panelGroup.panels.forEach((selectedPanel) => {
@ -363,14 +373,32 @@ const EditPanelsWizard: React.FC<EditPanelsWizardProps> = ({
configure &&
selectedPanel.panel_id === existingPanels[index].panel_id
) {
const existingPromQueries: PromQueryDetails[] = [];
selectedPanel.prom_queries.forEach((promQuery) => {
existingPromQueries.push({
hidden: false,
queryid: promQuery.queryid,
prom_query_name: promQuery.prom_query_name,
legend: promQuery.legend,
resolution: promQuery.resolution,
minstep: promQuery.minstep,
line: promQuery.line,
close_area: promQuery.close_area,
});
});
const existingPanelOptions: PanelOption = {
points: selectedPanel.panel_options.points,
grids: selectedPanel.panel_options.grids,
left_axis: selectedPanel.panel_options.left_axis,
};
existingPanels[index] = {
panel_id: selectedPanel.panel_id ?? '',
panel_group_id: panelGroup.panel_group_id ?? '',
created_at: selectedPanel.created_at ?? '',
panel_group_name: panelGroup.panel_group_name,
ds_url: dashboardVars.dataSourceURL ?? '',
prom_queries: selectedPanel.prom_queries,
panel_options: selectedPanel.panel_options,
prom_queries: existingPromQueries,
panel_options: existingPanelOptions,
panel_name: selectedPanel.panel_name,
y_axis_left: selectedPanel.y_axis_left,
y_axis_right: selectedPanel.y_axis_right,
@ -408,6 +436,9 @@ const EditPanelsWizard: React.FC<EditPanelsWizardProps> = ({
}
});
});
if (!configure && dashboardVars.dashboardTypeID === 'custom') {
existingPanels[index] = getNewPanel();
}
setDashboardDetails({
...dashboardDetails,
selectedPanels: existingPanels,
@ -446,13 +477,18 @@ const EditPanelsWizard: React.FC<EditPanelsWizardProps> = ({
scrollButtons="auto"
>
{dashboardDetails.selectedPanels?.map((panel, index) => (
<StyledTab label={panel.panel_name} {...a11yProps(index)} />
<StyledTab
label={panel.panel_name}
{...a11yProps(index)}
key={`tab-${panel.panel_group_name}-${panel.panel_name}`}
/>
))}
<StyledTab
label={t(
'analyticsDashboard.applicationDashboards.tuneTheQueries.addMetric'
)}
{...a11yProps(dashboardDetails.selectedPanels?.length)}
key="tab-addMetric"
/>
</Tabs>
</AppBar>

View File

@ -1,7 +1,7 @@
import { useMutation } from '@apollo/client';
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import { ButtonFilled, ButtonOutlined, Modal } from 'litmus-ui';
import { ButtonFilled, Modal, TextButton } from 'litmus-ui';
import moment from 'moment';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@ -13,9 +13,7 @@ import {
import useActions from '../../../../redux/actions';
import * as DashboardActions from '../../../../redux/actions/dashboards';
import * as DataSourceActions from '../../../../redux/actions/dataSource';
import * as TabActions from '../../../../redux/actions/tabs';
import { history } from '../../../../redux/configureStore';
import { ReactComponent as CrossMarkIcon } from '../../../../svg/crossmark.svg';
import {
getProjectID,
getProjectRole,
@ -24,59 +22,52 @@ import useStyles, { StyledTableCell } from './styles';
interface TableDataProps {
data: ListDashboardResponse;
alertStateHandler: (successState: boolean) => void;
}
const TableData: React.FC<TableDataProps> = ({ data }) => {
const TableData: React.FC<TableDataProps> = ({ data, alertStateHandler }) => {
const classes = useStyles();
const { t } = useTranslation();
const dashboard = useActions(DashboardActions);
const dataSource = useActions(DataSourceActions);
const tabs = useActions(TabActions);
const projectID = getProjectID();
const projectRole = getProjectRole();
const [mutate, setMutate] = React.useState(false);
const [confirm, setConfirm] = React.useState(false);
const [success, setSuccess] = React.useState(false);
const [openModal, setOpenModal] = React.useState(false);
const [dashboardSelectedForDeleting, setDashboardSelectedForDeleting] =
React.useState<DeleteDashboardInput>({
dbID: '',
});
const [deleteDashboard] = useMutation<boolean, DeleteDashboardInput>(
DELETE_DASHBOARD,
{
onCompleted: () => {
setSuccess(true);
setMutate(false);
setOpenModal(true);
},
onError: () => {
setMutate(false);
setOpenModal(true);
},
}
);
// Function to convert UNIX time in format of dddd, DD MMM YYYY, HH:mm
const formatDate = (date: string) => {
const updated = new Date(parseInt(date, 10) * 1000).toString();
const resDate = moment(updated).format('dddd, DD MMM YYYY, HH:mm');
return resDate;
};
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const [deleteDashboard] = useMutation<boolean, DeleteDashboardInput>(
DELETE_DASHBOARD,
{
onCompleted: () => {
setMutate(false);
alertStateHandler(true);
},
onError: () => {
setMutate(false);
alertStateHandler(false);
},
}
);
const onDashboardLoadRoutine = async () => {
dashboard.selectDashboard({
selectedDashboardID: data.db_id,
@ -100,40 +91,65 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
return (
<>
<StyledTableCell className={classes.dashboardName}>
<StyledTableCell className={classes.columnDivider}>
<Typography
variant="body2"
align="center"
className={classes.tableData}
className={`${classes.tableObjects} ${classes.dashboardNameCol} ${classes.dashboardNameColData}`}
onClick={() => {
onDashboardLoadRoutine().then(() => {
history.push({
pathname: '/analytics/application-dashboard',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
});
}}
>
<strong>{data.db_name}</strong>
{data.db_name}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<StyledTableCell className={classes.dividerPadding}>
<Typography
variant="body2"
align="center"
className={classes.tableData}
className={classes.tableObjects}
style={{ maxWidth: '5rem' }}
>
<strong>{data.cluster_name}</strong>
{data.cluster_name}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<Typography variant="body2" align="center">
<strong>{data.db_type_name}</strong>
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '7rem' }}
>
<img
src={`/icons/${data.db_type_id}_dashboard.svg`}
alt={data.db_type_name}
className={classes.inlineTypeIcon}
/>
{data.db_type_name}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<Typography variant="body2" align="center">
<strong>{data.ds_type}</strong>
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '5rem' }}
>
<img
src="/icons/prometheus.svg"
alt="Prometheus"
className={classes.inlineIcon}
/>
{data.ds_type}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<Typography variant="body2" align="center">
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '12.5rem' }}
>
<img src="/icons/calendarIcon.svg" alt="Calender" />
{formatDate(data.updated_at)}
</Typography>
</StyledTableCell>
@ -154,9 +170,19 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
keepMounted
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
getContentAnchorEl={null}
classes={{ paper: classes.menuList }}
>
<MenuItem
value="Analysis"
value="View"
onClick={() => {
onDashboardLoadRoutine().then(() => {
history.push({
@ -169,14 +195,12 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
>
<div className={classes.expDiv}>
<img
src="/icons/analytics.svg"
alt="See Analytics"
src="/icons/viewAnalytics.svg"
alt="View"
className={classes.btnImg}
/>
<Typography data-cy="openDashboard" className={classes.btnText}>
{t(
'analyticsDashboardViews.kubernetesDashboard.table.seeAnalytics'
)}
{t('analyticsDashboard.applicationDashboardTable.view')}
</Typography>
</div>
</MenuItem>
@ -205,9 +229,7 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
data-cy=" configureDashboard"
className={classes.btnText}
>
{t(
'analyticsDashboardViews.kubernetesDashboard.table.configure'
)}
{t('analyticsDashboard.applicationDashboardTable.configure')}
</Typography>
</div>
</MenuItem>
@ -219,6 +241,7 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
dbID: data.db_id,
});
setOpenModal(true);
handleClose();
}}
className={classes.menuItem}
>
@ -228,175 +251,62 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
alt="Delete"
className={classes.btnImg}
/>
<Typography data-cy="deleteDashboard" className={classes.btnText}>
Delete
<Typography
data-cy="deleteDashboard"
className={`${classes.btnText} ${classes.deleteText}`}
>
{t('analyticsDashboard.applicationDashboardTable.delete')}
</Typography>
</div>
</MenuItem>
</Menu>
</StyledTableCell>
<Modal
open={openModal}
onClose={() => {
setConfirm(false);
setOpenModal(false);
}}
width="60%"
modalActions={
<ButtonOutlined
className={classes.closeButton}
onClick={() => {
setConfirm(false);
setOpenModal(false);
}}
>
&#x2715;
</ButtonOutlined>
}
onClose={() => setOpenModal(false)}
width="45%"
height="fit-content"
>
<div className={classes.modal}>
{confirm === true ? (
<Typography align="center">
{success === true ? (
<img
src="/icons/finish.svg"
alt="success"
className={classes.icon}
/>
) : (
<CrossMarkIcon className={classes.icon} />
)}
</Typography>
) : (
<div />
)}
<Typography className={classes.modalHeading} align="left">
{t(
'analyticsDashboard.applicationDashboardTable.modal.removeDashboard'
)}
</Typography>
{confirm === true ? (
<Typography
className={classes.modalHeading}
align="center"
variant="h3"
<Typography className={classes.modalBodyText} align="left">
{t(
'analyticsDashboard.applicationDashboardTable.modal.removeDashboardConfirmation'
)}
<b>
<i>{` ${data.db_name} `}</i>
</b>
?
</Typography>
<div className={classes.flexButtons}>
<TextButton
onClick={() => setOpenModal(false)}
className={classes.cancelButton}
>
{success === true
? `The dashboard is successfully deleted.`
: `There was a problem deleting your dashboard.`}
</Typography>
) : (
<div />
)}
{confirm === true ? (
<Typography
align="center"
variant="body1"
className={classes.modalBody}
>
{success === true ? (
<div>
You will see the dashboard deleted in the dashboard table.
</div>
) : (
<div>
Error encountered while deleting the dashboard. Please try
again.
</div>
)}
</Typography>
) : (
<div />
)}
{success === true && confirm === true ? (
<Typography className={classes.buttonText}>
{t('analyticsDashboard.applicationDashboardTable.modal.cancel')}
</Typography>
</TextButton>
<ButtonFilled
variant="success"
onClick={() => {
setConfirm(false);
setMutate(true);
setOpenModal(false);
tabs.changeAnalyticsDashboardTabs(2);
window.location.reload();
}}
variant="error"
>
<div>Back to Kubernetes Dashboard</div>
<Typography
className={`${classes.buttonText} ${classes.confirmButtonText}`}
>
{t('analyticsDashboard.applicationDashboardTable.modal.delete')}
</Typography>
</ButtonFilled>
) : success === false && confirm === true ? (
<div className={classes.flexButtons}>
<ButtonOutlined
className={classes.buttonOutlineWarning}
onClick={() => {
setOpenModal(false);
setMutate(true);
}}
disabled={false}
>
<div>Try Again</div>
</ButtonOutlined>
<ButtonFilled
variant="success"
onClick={() => {
setConfirm(false);
setOpenModal(false);
tabs.changeAnalyticsDashboardTabs(2);
window.location.reload();
}}
>
<div>Back to Kubernetes Dashboard</div>
</ButtonFilled>
</div>
) : (
<div>
<Typography align="center">
<img
src="/icons/delete_large_icon.svg"
alt="delete"
className={classes.icon}
/>
</Typography>
<Typography
className={classes.modalHeading}
align="center"
variant="h3"
>
Are you sure to remove this dashboard?
</Typography>
<Typography
align="center"
variant="body1"
className={classes.modalBody}
>
The following action cannot be reverted.
</Typography>
<div className={classes.flexButtons}>
<ButtonOutlined
onClick={() => {
setOpenModal(false);
history.push({
pathname: '/analytics',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
disabled={false}
>
<div>No</div>
</ButtonOutlined>
<ButtonFilled
variant="error"
onClick={() => {
setConfirm(true);
setOpenModal(false);
setMutate(true);
}}
>
<div>Yes</div>
</ButtonFilled>
</div>
</div>
)}
</div>
</div>
</Modal>
</>

View File

@ -1,4 +1,4 @@
import { IconButton, TableHead, TableRow } from '@material-ui/core';
import { IconButton, TableHead, TableRow, Typography } from '@material-ui/core';
import ExpandLessTwoToneIcon from '@material-ui/icons/ExpandLessTwoTone';
import ExpandMoreTwoToneIcon from '@material-ui/icons/ExpandMoreTwoTone';
import React, { useEffect, useState } from 'react';
@ -8,9 +8,6 @@ import useStyles, { StyledTableCell } from './styles';
interface SortData {
lastViewed: { sort: boolean; ascending: boolean };
name: { sort: boolean; ascending: boolean };
agent: { sort: boolean; ascending: boolean };
dataSourceType: { sort: boolean; ascending: boolean };
dashboardType: { sort: boolean; ascending: boolean };
}
interface SortCallBackType {
@ -28,9 +25,6 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
const [sortData, setSortData] = useState<SortData>({
name: { sort: false, ascending: true },
lastViewed: { sort: true, ascending: false },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
});
useEffect(() => {
@ -39,12 +33,16 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
return (
<TableHead>
<TableRow className={classes.tableHead}>
<StyledTableCell className={classes.dashboardName}>
<TableRow>
<StyledTableCell
className={`${classes.headSpacing} ${classes.columnDivider}`}
>
<div className={classes.nameContent}>
<div className={classes.dashboardNameHead}>
<b>{t('analyticsDashboard.dashboardTable.tableHead1')}</b>&nbsp;
</div>
<Typography
className={`${classes.dashboardNameHead} ${classes.dashboardNameCol}`}
>
{t('analyticsDashboard.applicationDashboardTable.tableHead1')}
</Typography>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort name ascending"
@ -54,13 +52,10 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: true, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
<ExpandLessTwoToneIcon className={classes.markerIcon} />
</IconButton>
<IconButton
aria-label="sort name descending"
@ -70,145 +65,42 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: true, ascending: false },
lastViewed: { sort: false, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
<ExpandMoreTwoToneIcon className={classes.markerIcon} />
</IconButton>
</div>
</div>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<div className={classes.nameContent}>
<div className={classes.dashboardNameHead}>
<b>{t('analyticsDashboard.dashboardTable.tableHead2')}</b>&nbsp;
</div>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort agent name ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: true, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
</IconButton>
<IconButton
aria-label="sort agent name descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: true, ascending: false },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
</IconButton>
</div>
</div>
<StyledTableCell
className={`${classes.headSpacing} ${classes.dividerPadding}`}
>
<Typography
className={`${classes.dashboardNameHead} ${classes.dashboardNameHeadWithoutSort}`}
>
{t('analyticsDashboard.applicationDashboardTable.tableHead2')}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<div className={classes.nameContent}>
<div className={classes.dashboardNameHead}>
<b>{t('analyticsDashboard.dashboardTable.tableHead3')}</b>&nbsp;
</div>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort dashboard type ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: true, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
</IconButton>
<IconButton
aria-label="sort dashboard type descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: true, ascending: false },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
</IconButton>
</div>
</div>
<StyledTableCell className={classes.headSpacing}>
<Typography
className={`${classes.dashboardNameHead} ${classes.dashboardNameHeadWithoutSort}`}
>
{t('analyticsDashboard.applicationDashboardTable.tableHead3')}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<div className={classes.nameContent}>
<div className={classes.dashboardNameHead}>
<b>{t('analyticsDashboard.dashboardTable.tableHead4')}</b>&nbsp;
</div>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort data source type ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: true, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
</IconButton>
<IconButton
aria-label="sort data source type descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: false, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: true, ascending: false },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
</IconButton>
</div>
</div>
<StyledTableCell className={classes.headSpacing}>
<Typography
className={`${classes.dashboardNameHead} ${classes.dashboardNameHeadWithoutSort}`}
>
{t('analyticsDashboard.applicationDashboardTable.tableHead4')}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.tableHeader}>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.dashboardNameHead}>
<b>{t('analyticsDashboard.dashboardTable.tableHead5')}</b>&nbsp;
</div>
<Typography className={classes.dashboardNameHead}>
{t('analyticsDashboard.applicationDashboardTable.tableHead5')}
</Typography>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort last viewed ascending"
@ -218,13 +110,10 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: true, ascending: true },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
<ExpandLessTwoToneIcon className={classes.markerIcon} />
</IconButton>
<IconButton
aria-label="sort last viewed descending"
@ -234,13 +123,10 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: false, ascending: true },
lastViewed: { sort: true, ascending: false },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
<ExpandMoreTwoToneIcon className={classes.markerIcon} />
</IconButton>
</div>
</div>

View File

@ -2,18 +2,15 @@ import {
Button,
FormControl,
IconButton,
InputAdornment,
InputBase,
InputLabel,
MenuItem,
OutlinedInput,
Select,
Typography,
} from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import SearchIcon from '@material-ui/icons/Search';
import { ButtonFilled } from 'litmus-ui';
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
import { ButtonFilled, Search } from 'litmus-ui';
import React, { ChangeEvent, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import DateRangeSelector from '../../../../components/DateRangeSelector';
@ -53,6 +50,7 @@ interface TableToolBarProps {
callbackToSetRange: RangeCallBackType;
callbackToSetDashboardType: DashboardTypeCallBackType;
callbackToSetAgentName: AgentNameCallBackType;
createButtonDisabled: boolean;
}
interface RangeType {
@ -70,6 +68,7 @@ const TableToolBar: React.FC<TableToolBarProps> = ({
callbackToSetDataSourceType,
callbackToSetDashboardType,
callbackToSetAgentName,
createButtonDisabled,
}) => {
const classes = useStyles();
const outlinedInputClasses = useOutlinedInputStyles();
@ -125,145 +124,200 @@ const TableToolBar: React.FC<TableToolBarProps> = ({
return (
<div className={classes.headerSection}>
<InputBase
id="input-with-icon-adornment"
placeholder="Search"
className={classes.search}
value={searchToken}
onChange={handleSearch}
classes={{
input: classes.input,
}}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
}
/>
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dashboardTypeForm}`}
<div className={classes.search}>
<Search
id="input-with-icon-textfield"
placeholder={t('analyticsDashboard.applicationDashboardTable.search')}
value={searchToken}
onChange={handleSearch}
/>
</div>
<div
className={classes.headerSection}
style={{ justifyContent: 'flex-end' }}
>
<InputLabel className={classes.selectText}> Agent Name </InputLabel>
<Select
label="Agent Name"
value={agentName}
onChange={handleAgentNameChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dashboardTypeForm}`}
>
<MenuItem value="All">All</MenuItem>
{agentNames.map((agentName: string) => (
<MenuItem
key={`${agentName}-kubernetesDashboard-Toolbar`}
value={agentName}
>
{agentName}
<InputLabel className={classes.selectText}>
{t('analyticsDashboard.applicationDashboardTable.tableHead2')}
</InputLabel>
<Select
label={t('analyticsDashboard.applicationDashboardTable.tableHead2')}
value={agentName}
onChange={handleAgentNameChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
IconComponent={KeyboardArrowDownIcon}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
}}
>
<MenuItem value="All" className={classes.menuListItem}>
{t('analyticsDashboard.applicationDashboardTable.all')}
</MenuItem>
))}
</Select>
</FormControl>
{agentNames.map((availableAgentName: string) => (
<MenuItem
key={`${availableAgentName}-kubernetesDashboard-Toolbar`}
value={availableAgentName}
className={classes.menuListItem}
>
{availableAgentName}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dashboardTypeForm}`}
>
<InputLabel className={classes.selectText}> Dashboard Type </InputLabel>
<Select
label="Dashboard Type"
value={dashboardType}
onChange={handleDashboardTypeChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dashboardTypeForm}`}
>
<MenuItem value="All">All</MenuItem>
{dashboardTypes.map((dashboardType: string) => (
<MenuItem
key={`${dashboardType}-kubernetesDashboard-toolbar`}
value={dashboardType}
>
{dashboardType}
<InputLabel className={classes.selectText}>
{t('analyticsDashboard.applicationDashboardTable.tableHead3')}
</InputLabel>
<Select
label={t('analyticsDashboard.applicationDashboardTable.tableHead3')}
value={dashboardType}
onChange={handleDashboardTypeChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
IconComponent={KeyboardArrowDownIcon}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
}}
>
<MenuItem value="All" className={classes.menuListItem}>
{t('analyticsDashboard.applicationDashboardTable.all')}
</MenuItem>
))}
</Select>
</FormControl>
{dashboardTypes.map((availableDashboardType: string) => (
<MenuItem
key={`${availableDashboardType}-kubernetesDashboard-toolbar`}
value={availableDashboardType}
className={classes.menuListItem}
>
{availableDashboardType}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dataSourceTypeForm}`}
>
<InputLabel className={classes.selectText}>
{' '}
Data Source Type{' '}
</InputLabel>
<Select
label="Data Source Type"
value={dataSourceType}
onChange={handleDataSourceTypeChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dataSourceTypeForm}`}
>
<MenuItem value="All">All</MenuItem>
{dataSourceTypes.map((dataSourceType: string) => (
<MenuItem
key={`${dataSourceType}-KubernetesDashboard-TableToolbar`}
value={dataSourceType}
>
{dataSourceType}
<InputLabel className={classes.selectText}>
{t('analyticsDashboard.applicationDashboardTable.tableHead4')}
</InputLabel>
<Select
label={t('analyticsDashboard.applicationDashboardTable.tableHead4')}
value={dataSourceType}
onChange={handleDataSourceTypeChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
IconComponent={KeyboardArrowDownIcon}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
}}
>
<MenuItem value="All" className={classes.menuListItem}>
{t('analyticsDashboard.applicationDashboardTable.all')}
</MenuItem>
))}
</Select>
</FormControl>
{dataSourceTypes.map((availableDataSourceType: string) => (
<MenuItem
key={`${availableDataSourceType}-ApplicationDashboards-TableToolbar`}
value={availableDataSourceType}
className={classes.menuListItem}
>
{availableDataSourceType}
</MenuItem>
))}
</Select>
</FormControl>
<Button
className={classes.selectDate}
onClick={() => setDateRangeSelectorPopoverOpen(true)}
ref={dateRangeSelectorRef}
aria-label="time range"
aria-haspopup="true"
>
<Typography className={classes.displayDate}>
{range.startDate === ' '
? 'Select Period'
: `${range.startDate.split(' ')[2]} ${
range.startDate.split(' ')[1]
} ${range.startDate.split(' ')[3]} - ${
range.endDate.split(' ')[2]
} ${range.endDate.split(' ')[1]} ${range.endDate.split(' ')[3]}`}
<Button
className={`${classes.selectDate} ${
isDateRangeSelectorPopoverOpen ? classes.selectDateFocused : ''
}`}
onClick={() => setDateRangeSelectorPopoverOpen(true)}
ref={dateRangeSelectorRef}
aria-label="time range"
aria-haspopup="true"
>
<Typography className={classes.displayDate}>
{range.startDate === ' '
? t('analyticsDashboard.applicationDashboardTable.selectPeriod')
: `${range.startDate.split(' ')[2]} ${
range.startDate.split(' ')[1]
} ${range.startDate.split(' ')[3]} - ${
range.endDate.split(' ')[2]
} ${range.endDate.split(' ')[1]} ${
range.endDate.split(' ')[3]
}`}
<IconButton className={classes.rangeSelectorIcon}>
{isDateRangeSelectorPopoverOpen ? (
<KeyboardArrowDownIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</Typography>
</Button>
<ButtonFilled
className={classes.addButton}
size="small"
onClick={() => {
history.push({
pathname: '/analytics/dashboard/create',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
>
<Typography className={classes.dateRangeDefault}>
{t('analyticsDashboard.dashboardTable.addDashboard')}
</Typography>
</ButtonFilled>
<DateRangeSelector
anchorEl={dateRangeSelectorRef.current as HTMLElement}
isOpen={isDateRangeSelectorPopoverOpen}
onClose={() => {
setDateRangeSelectorPopoverOpen(false);
}}
callbackToSetRange={CallbackFromRangeSelector}
/>
<IconButton className={classes.rangeSelectorIcon}>
{isDateRangeSelectorPopoverOpen ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</Typography>
</Button>
<ButtonFilled
onClick={() =>
history.push({
pathname: '/analytics/dashboard/create',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
})
}
className={classes.createButton}
disabled={createButtonDisabled}
>
<Typography
className={`${classes.buttonText} ${
createButtonDisabled ? classes.disabledText : ''
}`}
>
{t('analyticsDashboard.applicationDashboardTable.createDashboard')}
</Typography>
</ButtonFilled>
<DateRangeSelector
anchorEl={dateRangeSelectorRef.current as HTMLElement}
isOpen={isDateRangeSelectorPopoverOpen}
onClose={() => {
setDateRangeSelectorPopoverOpen(false);
}}
callbackToSetRange={CallbackFromRangeSelector}
/>
</div>
</div>
);
};

View File

@ -2,6 +2,7 @@
import { useQuery } from '@apollo/client';
import {
Paper,
Snackbar,
Table,
TableBody,
TableCell,
@ -10,17 +11,30 @@ import {
TableRow,
Typography,
} from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { TextButton } from 'litmus-ui';
import moment from 'moment';
import React from 'react';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Loader from '../../../../components/Loader';
import { LIST_DASHBOARD } from '../../../../graphql/queries';
import { LIST_DASHBOARD, LIST_DATASOURCE } from '../../../../graphql/queries';
import {
DashboardList,
ListDashboardResponse,
ListDashboardVars,
} from '../../../../models/graphql/dashboardsDetails';
import { getProjectID } from '../../../../utils/getSearchParams';
import {
DataSourceList,
ListDataSourceResponse,
ListDataSourceVars,
} from '../../../../models/graphql/dataSourceDetails';
import useActions from '../../../../redux/actions';
import * as TabActions from '../../../../redux/actions/tabs';
import { history } from '../../../../redux/configureStore';
import {
getProjectID,
getProjectRole,
} from '../../../../utils/getSearchParams';
import {
sortAlphaAsc,
sortAlphaDesc,
@ -40,9 +54,6 @@ interface RangeType {
interface SortData {
lastViewed: { sort: boolean; ascending: boolean };
name: { sort: boolean; ascending: boolean };
agent: { sort: boolean; ascending: boolean };
dataSourceType: { sort: boolean; ascending: boolean };
dashboardType: { sort: boolean; ascending: boolean };
}
interface Filter {
@ -64,9 +75,6 @@ const DashboardTable: React.FC = () => {
sortData: {
name: { sort: false, ascending: true },
lastViewed: { sort: true, ascending: false },
agent: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
dashboardType: { sort: false, ascending: true },
},
selectedAgentName: 'All',
searchTokens: [''],
@ -74,6 +82,38 @@ const DashboardTable: React.FC = () => {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const projectID = getProjectID();
const projectRole = getProjectRole();
const tabs = useActions(TabActions);
const [isAlertOpen, setIsAlertOpen] = React.useState(false);
const [success, setSuccess] = React.useState(false);
const [activeDataSourceAvailable, setActiveDataSourceAvailable] =
React.useState(false);
// Apollo query to get the dashboard data
const { data, loading, error, refetch } = useQuery<
DashboardList,
ListDashboardVars
>(LIST_DASHBOARD, {
variables: { projectID },
fetchPolicy: 'cache-and-network',
});
// Apollo query to get the data source data
const { data: dataSourceList, loading: loadingDataSources } = useQuery<
DataSourceList,
ListDataSourceVars
>(LIST_DATASOURCE, {
variables: { projectID },
fetchPolicy: 'cache-and-network',
});
const alertStateHandler = (successState: boolean) => {
setSuccess(successState);
setIsAlertOpen(true);
if (successState) {
refetch();
}
};
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
@ -86,15 +126,6 @@ const DashboardTable: React.FC = () => {
setPage(0);
};
// Apollo query to get the dashboard data
const { data, loading, error } = useQuery<DashboardList, ListDashboardVars>(
LIST_DASHBOARD,
{
variables: { projectID },
fetchPolicy: 'cache-and-network',
}
);
const getDataSourceType = (searchingData: ListDashboardResponse[]) => {
const uniqueList: string[] = [];
searchingData.forEach((data) => {
@ -108,8 +139,8 @@ const DashboardTable: React.FC = () => {
const getDashboardType = (searchingData: ListDashboardResponse[]) => {
const uniqueList: string[] = [];
searchingData.forEach((data) => {
if (!uniqueList.includes(data.db_type)) {
uniqueList.push(data.db_type);
if (!uniqueList.includes(data.db_type_name)) {
uniqueList.push(data.db_type_name);
}
});
return uniqueList;
@ -129,12 +160,8 @@ const DashboardTable: React.FC = () => {
? !data.ListDashboard
? []
: data.ListDashboard.filter((db: ListDashboardResponse) => {
return filter.searchTokens.every(
(s: string) =>
db.db_name.toLowerCase().includes(s) ||
db.db_type.toLowerCase().includes(s) ||
db.ds_type.toLowerCase().includes(s) ||
db.cluster_name.toLowerCase().includes(s)
return filter.searchTokens.every((s: string) =>
db.db_name.toLowerCase().includes(s)
);
})
.filter((data) => {
@ -145,7 +172,7 @@ const DashboardTable: React.FC = () => {
.filter((data) => {
return filter.selectedDashboardType === 'All'
? true
: data.db_type === filter.selectedDashboardType;
: data.db_type_name === filter.selectedDashboardType;
})
.filter((data) => {
return filter.selectedAgentName === 'All'
@ -183,33 +210,79 @@ const DashboardTable: React.FC = () => {
? sortNumAsc(y, x)
: sortNumDesc(y, x);
}
if (filter.sortData.dashboardType.sort) {
const x = a.db_type;
const y = b.db_type;
return filter.sortData.dashboardType.ascending
? sortAlphaAsc(x, y)
: sortAlphaDesc(x, y);
}
if (filter.sortData.dataSourceType.sort) {
const x = a.ds_type;
const y = b.ds_type;
return filter.sortData.dataSourceType.ascending
? sortAlphaAsc(x, y)
: sortAlphaDesc(x, y);
}
if (filter.sortData.agent.sort) {
const x = a.cluster_name;
const y = b.cluster_name;
return filter.sortData.agent.ascending
? sortAlphaAsc(x, y)
: sortAlphaDesc(x, y);
}
return 0;
})
: [];
useEffect(() => {
if (dataSourceList && dataSourceList.ListDataSource) {
const activeDataSources: ListDataSourceResponse[] =
dataSourceList.ListDataSource.filter(
(dataSource) => dataSource.health_status === 'Active'
) ?? [];
if (activeDataSources.length) {
setActiveDataSourceAvailable(true);
}
}
}, [dataSourceList]);
return (
<div className={classes.root}>
{!activeDataSourceAvailable && !loadingDataSources && (
<blockquote className={classes.warningBlock}>
<Typography className={classes.warningText} align="left">
{dataSourceList?.ListDataSource.length
? t(
'analyticsDashboard.applicationDashboardTable.warning.noActiveDataSource'
)
: t(
'analyticsDashboard.applicationDashboardTable.warning.noAvailableDataSource'
)}
</Typography>
<div className={classes.warningActions}>
{dataSourceList && dataSourceList.ListDataSource.length > 0 && (
<>
<TextButton
onClick={() => tabs.changeAnalyticsDashboardTabs(3)}
variant="highlight"
className={classes.warningButton}
>
<Typography
className={classes.buttonText}
style={{ fontWeight: 500 }}
>
{t(
'analyticsDashboard.applicationDashboardTable.warning.configureExisting'
)}
</Typography>
</TextButton>
<Typography className={classes.orText}>
{t('analyticsDashboard.applicationDashboardTable.warning.or')}
</Typography>
</>
)}
<TextButton
onClick={() =>
history.push({
pathname: '/analytics/datasource/create',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
})
}
variant="highlight"
className={classes.warningButton}
>
<Typography
className={classes.buttonText}
style={{ fontWeight: 500 }}
>
{t(
'analyticsDashboard.applicationDashboardTable.warning.addNew'
)}
</Typography>
</TextButton>
</div>
</blockquote>
)}
<Paper>
<section className="Heading section">
<TableToolBar
@ -229,53 +302,60 @@ const DashboardTable: React.FC = () => {
.filter((s) => s !== ''),
})
}
dataSourceTypes={getDataSourceType(payload)}
dashboardTypes={getDashboardType(payload)}
agentNames={getAgentName(payload)}
callbackToSetDataSourceType={(dataSourceType: string) => {
dataSourceTypes={getDataSourceType(data?.ListDashboard ?? [])}
dashboardTypes={getDashboardType(data?.ListDashboard ?? [])}
agentNames={getAgentName(data?.ListDashboard ?? [])}
callbackToSetDataSourceType={(dataSourceType: string) =>
setFilter({
...filter,
selectedDataSourceType: dataSourceType,
});
}}
callbackToSetDashboardType={(dashboardType: string) => {
})
}
callbackToSetDashboardType={(dashboardType: string) =>
setFilter({
...filter,
selectedDashboardType: dashboardType,
});
}}
callbackToSetAgentName={(agentName: string) => {
})
}
callbackToSetAgentName={(agentName: string) =>
setFilter({
...filter,
selectedAgentName: agentName,
});
}}
})
}
callbackToSetRange={(
selectedStartDate: string,
selectedEndDate: string
) => {
) =>
setFilter({
...filter,
range: {
startDate: selectedStartDate,
endDate: selectedEndDate,
},
});
}}
})
}
createButtonDisabled={
!activeDataSourceAvailable && !loadingDataSources
}
/>
</section>
</Paper>
<Paper>
<section className="table section">
<TableContainer className={classes.tableMain}>
<TableContainer
className={`${classes.tableMain} ${
!payload.length || loading ? classes.minHeight : ''
}`}
>
<Table aria-label="simple table">
<TableHeader
callBackToSort={(sortConfigurations: SortData) => {
callBackToSort={(sortConfigurations: SortData) =>
setFilter({
...filter,
sortData: sortConfigurations,
});
}}
})
}
/>
<TableBody>
{error ? (
@ -283,7 +363,7 @@ const DashboardTable: React.FC = () => {
<TableCell colSpan={6}>
<Typography align="center">
{t(
'analyticsDashboardViews.kubernetesDashboard.table.error'
'analyticsDashboard.applicationDashboardTable.error'
)}
</Typography>
</TableCell>
@ -291,22 +371,33 @@ const DashboardTable: React.FC = () => {
) : loading ? (
<TableRow>
<TableCell colSpan={6}>
<Loader />
<Typography align="center">
{t(
'analyticsDashboardViews.kubernetesDashboard.table.loading'
)}
</Typography>
<div
className={`${classes.noRecords} ${classes.loading}`}
>
<Loader />
<Typography align="center">
{t(
'analyticsDashboard.applicationDashboardTable.loading'
)}
</Typography>
</div>
</TableCell>
</TableRow>
) : !payload.length ? (
<TableRow>
<TableCell colSpan={6}>
<Typography align="center">
{t(
'analyticsDashboardViews.kubernetesDashboard.table.noRecords'
)}
</Typography>
<div className={classes.noRecords}>
<img
src="/icons/dashboardUnavailable.svg"
className={classes.unavailableIcon}
alt="Dashboard"
/>
<Typography className={classes.noRecordsText}>
{t(
'analyticsDashboard.applicationDashboardTable.noRecords'
)}
</Typography>
</div>
</TableCell>
</TableRow>
) : payload.length > 0 ? (
@ -321,7 +412,10 @@ const DashboardTable: React.FC = () => {
key={data.db_id}
className={classes.tableRow}
>
<TableData data={data} />
<TableData
data={data}
alertStateHandler={alertStateHandler}
/>
</TableRow>
);
})
@ -330,7 +424,7 @@ const DashboardTable: React.FC = () => {
<TableCell colSpan={6}>
<Typography align="center">
{t(
'analyticsDashboardViews.kubernetesDashboard.table.noRecords'
'analyticsDashboard.applicationDashboardTable.noRecords'
)}
</Typography>
</TableCell>
@ -348,9 +442,42 @@ const DashboardTable: React.FC = () => {
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
className={classes.tablePagination}
SelectProps={{
MenuProps: {
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
},
}}
classes={{ menuItem: classes.menuListItem }}
/>
</section>
</Paper>
{isAlertOpen && (
<Snackbar
open={isAlertOpen}
autoHideDuration={3000}
onClose={() => setIsAlertOpen(false)}
>
<Alert
onClose={() => setIsAlertOpen(false)}
severity={success ? 'success' : 'error'}
>
{success
? t(
'analyticsDashboard.applicationDashboardTable.deletionSuccess'
)
: t('analyticsDashboard.applicationDashboardTable.deletionError')}
</Alert>
</Snackbar>
)}
</div>
);
};

View File

@ -15,37 +15,28 @@ const useStyles = makeStyles((theme) => ({
marginTop: theme.spacing(3),
},
headerSection: {
width: '100%',
warningBlock: {
margin: theme.spacing(0, 0, 4),
padding: theme.spacing(0.5, 3),
borderLeft: `5px solid ${theme.palette.warning.main}`,
borderRadius: '5px 0 0 5px',
background: theme.palette.warning.light,
display: 'flex',
flexDirection: 'row',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
height: '6rem',
backgroundColor: theme.palette.background.paper,
justifyContent: 'space-between',
},
headerIcon: {
color: theme.palette.text.primary,
opacity: 0.6,
warningText: {
margin: theme.spacing(1.5, 0),
fontSize: '1rem',
lineHeight: '130%',
fontFeatureSettings: 'pnum on, lnum on',
},
input: {
'&:-webkit-autofill': {
WebkitTextFillColor: theme.palette.text.primary,
WebkitBoxShadow: `0 0 0 1000px ${theme.palette.background.paper} inset`,
},
warningActions: {
display: 'flex',
margin: theme.spacing(1.5),
},
tableRow: {
height: '4.5rem',
},
search: {
marginRight: 'auto',
marginLeft: theme.spacing(6.25),
borderBottom: `1px solid ${theme.palette.border.main}`,
warningButton: {
padding: 0,
minHeight: 0,
},
tableMain: {
@ -55,6 +46,7 @@ const useStyles = makeStyles((theme) => ({
minHeight: '25rem',
'&::-webkit-scrollbar': {
width: '0.2em',
height: '0.2em',
},
'&::-webkit-scrollbar-track': {
webkitBoxShadow: `inset 0 0 6px ${theme.palette.common.black}`,
@ -67,28 +59,83 @@ const useStyles = makeStyles((theme) => ({
},
},
tableHead: {
opacity: 0.7,
color: theme.palette.text.primary,
tableRow: {
height: '4.5rem',
},
dashboardName: {
borderRight: `1px solid ${theme.palette.border.main}`,
minWidth: '10rem',
paddingLeft: theme.spacing(5),
headerSection: {
width: '100%',
display: 'flex',
flexDirection: 'row',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'space-between',
overflowX: 'auto',
height: '6rem',
backgroundColor: theme.palette.background.paper,
},
tableHeader: {
minWidth: '12rem',
search: {
marginLeft: theme.spacing(6),
},
minHeight: {
height: '20rem',
minHeight: '20rem',
},
noRecords: {
height: '12.5rem',
display: 'flex',
padding: theme.spacing(7.5, 3, 5),
justifyContent: 'center',
},
loading: {
flexDirection: 'column',
padding: theme.spacing(2, 3, 7.5),
},
unavailableIcon: {
height: '3.5rem',
width: '3.5rem',
marginTop: theme.spacing(0.65),
},
noRecordsText: {
color: theme.palette.text.hint,
padding: theme.spacing(2),
fontSize: '1.5rem',
lineHeight: '150%',
letterSpacing: '0.1176px',
textAlign: 'center',
},
dashboardNameHead: {
margin: '0 auto',
marginTop: theme.spacing(2.5),
fontWeight: 500,
fontSize: '0.75rem',
lineHeight: '150%',
color: theme.palette.text.hint,
},
dashboardType: {
marginTop: theme.spacing(2.5),
dashboardNameHeadWithoutSort: {
marginTop: theme.spacing(0.75),
},
dashboardNameCol: {
paddingLeft: theme.spacing(4),
},
dashboardNameColData: {
maxWidth: '10rem',
fontWeight: 500,
cursor: 'pointer',
},
headSpacing: {
minWidth: '5rem',
paddingLeft: theme.spacing(2),
},
nameContent: {
@ -102,43 +149,17 @@ const useStyles = makeStyles((theme) => ({
justifyContent: 'center',
},
markerIconDown: {
markerIcon: {
color: theme.palette.text.hint,
paddingTop: theme.spacing(0.5),
margin: 0,
},
markerIconUp: {
color: theme.palette.text.hint,
paddingTop: theme.spacing(0.5),
margin: 0,
},
tableData: {
paddingRight: theme.spacing(2),
},
options: {
minWidth: '4rem',
minWidth: '3rem',
paddingRight: theme.spacing(2),
},
// Menu option with icon
expDiv: {
display: 'flex',
flexDirection: 'row',
},
btnImg: {
width: '0.8125rem',
height: '0.8125rem',
marginTop: theme.spacing(0.375),
},
btnText: {
paddingLeft: theme.spacing(1.625),
},
tablePagination: {
marginTop: theme.spacing(-0.25),
borderTop: `1px solid ${theme.palette.border.main}`,
@ -152,12 +173,19 @@ const useStyles = makeStyles((theme) => ({
selectDate: {
display: 'flex',
height: '2.9rem',
height: '2.75rem',
minWidth: '9rem',
border: `0.1px solid ${theme.palette.border.main}`,
borderRadius: 4,
borderRadius: '0.25rem',
marginRight: theme.spacing(1.5),
textTransform: 'none',
'&:hover': {
borderColor: theme.palette.highlight,
},
},
selectDateFocused: {
border: `2px solid ${theme.palette.highlight}`,
},
rangeSelectorIcon: {
@ -165,10 +193,38 @@ const useStyles = makeStyles((theme) => ({
height: '0.625rem',
},
tableObjects: {
display: 'flex',
gap: '0.5rem',
textAlign: 'left',
color: theme.palette.text.primary,
fontSize: '0.75rem',
lineHeight: '150%',
},
inlineIcon: {
margin: theme.spacing(0.25, 0),
width: '1rem',
height: '1rem',
},
inlineTypeIcon: {
width: '1.25rem',
height: '1.25rem',
},
columnDivider: {
borderRight: `1px solid ${theme.palette.border.main}`,
},
dividerPadding: {
paddingLeft: theme.spacing(4),
},
// Form Select Properties
formControl: {
margin: theme.spacing(0.5),
height: '2.8rem',
height: '2.6rem',
minWidth: '9rem',
},
@ -186,50 +242,97 @@ const useStyles = makeStyles((theme) => ({
padding: theme.spacing(0.4),
},
createButton: {
margin: theme.spacing(0, 3, 0, 1),
padding: theme.spacing(0, 2.5),
},
// Menu option with icon
menuItem: {
width: '10rem',
height: '2.5rem',
'&:hover': {
background: theme.palette.cards.highlight,
},
},
dateRangeDefault: {
padding: theme.spacing(1),
headerIcon: {
color: theme.palette.border.main,
},
addButton: {
margin: theme.spacing(0, 2),
},
icon: {
width: '6rem',
height: '6rem',
},
modalHeading: {
marginTop: theme.spacing(3.5),
fontSize: '2.25rem',
marginBottom: theme.spacing(4.5),
},
modalBody: {
marginBottom: theme.spacing(4.5),
},
closeButton: {
borderColor: theme.palette.border.main,
},
flexButtons: {
expDiv: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-evenly',
},
btnImg: {
width: '0.8125rem',
height: '0.8125rem',
marginTop: theme.spacing(0.375),
},
btnText: {
paddingLeft: theme.spacing(1.625),
},
deleteText: {
color: theme.palette.error.main,
},
buttonOutlineWarning: {
borderColor: theme.palette.error.dark,
// modal
modalHeading: {
fontSize: '1.5rem',
lineHeight: '130%',
fontWeight: 'bold',
fontFeatureSettings: 'pnum on, lnum on',
margin: theme.spacing(2.5, 0, 4.5),
padding: theme.spacing(0, 6.5),
},
modalBodyText: {
fontSize: '1rem',
lineHeight: '130%',
padding: theme.spacing(0, 6.5),
},
flexButtons: {
display: 'flex',
justifyContent: 'flex-end',
padding: theme.spacing(5.5, 6.5, 0, 0),
},
modal: {
padding: theme.spacing(15, 0),
padding: theme.spacing(5, 0),
},
buttonText: {
lineHeight: '140%',
fontSize: '0.875rem',
},
orText: {
lineHeight: '140%',
fontSize: '0.875rem',
fontWeight: 500,
padding: theme.spacing(0, 2),
},
disabledText: {
color: theme.palette.text.disabled,
},
confirmButtonText: {
color: theme.palette.text.secondary,
padding: theme.spacing(0, 3),
},
cancelButton: {
marginRight: theme.spacing(1.5),
padding: theme.spacing(0, 3),
},
// select
menuList: {
boxShadow: '0 5px 9px rgba(0, 0, 0, 0.1)',
},
menuListItem: {
background: `${theme.palette.background.paper} !important`,
fontSize: '0.875rem',
lineHeight: '150%',
height: '1.875rem',
'&:hover': {
background: `${theme.palette.cards.highlight} !important`,
},
'&.Mui-selected': {
background: `${theme.palette.cards.highlight} !important`,
},
},
}));
@ -239,12 +342,12 @@ export const useOutlinedInputStyles = makeStyles((theme: Theme) => ({
borderColor: theme.palette.border.main,
},
'&:hover $notchedOutline': {
borderColor: theme.palette.border.main,
borderColor: theme.palette.highlight,
},
'&$focused $notchedOutline': {
borderColor: theme.palette.border.main,
borderColor: theme.palette.highlight,
},
height: '2.9rem',
height: '2.75rem',
},
focused: {},
notchedOutline: {},

View File

@ -1,12 +1,7 @@
/* eslint-disable no-unused-expressions */
import { useQuery } from '@apollo/client';
import {
FormControlLabel,
FormGroup,
Icon,
Typography,
} from '@material-ui/core';
import { InputField, RadioButton } from 'litmus-ui';
import { FormControlLabel, FormGroup, Typography } from '@material-ui/core';
import { InputField, RadioButton, TextButton } from 'litmus-ui';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LIST_DATASOURCE } from '../../../../graphql';
@ -16,6 +11,8 @@ import {
ListDataSourceResponse,
ListDataSourceVars,
} from '../../../../models/graphql/dataSourceDetails';
import { ReactComponent as ExternalLinkIcon } from '../../../../svg/externalLink.svg';
import { ReactComponent as DocsIcon } from '../../../../svg/prometheusDocs.svg';
import { getProjectID } from '../../../../utils/getSearchParams';
import {
isValidWebUrl,
@ -226,29 +223,22 @@ const ConfigurePrometheus: React.FC<ConfigurePrometheusProps> = ({
/>
</div>
</div>
<div className={classes.iconWithTextDiv}>
<img
src="/icons/docs.svg"
alt="Docs icon"
className={classes.inlineIcon}
/>
<TextButton
className={classes.button}
onClick={() =>
window.open(
'https://github.com/litmuschaos/litmus/tree/master/monitoring#model-1-optional-prometheus-scrape-config-model'
)
}
startIcon={<DocsIcon className={classes.inlineIcon} />}
endIcon={<ExternalLinkIcon className={classes.inlineIcon} />}
classes={{ label: classes.buttonLabel }}
variant="highlight"
>
<Typography className={classes.infoValue}>
{t('analyticsDashboard.dataSourceForm.docsAndSetup')}
</Typography>
<Icon
onClick={() => {
window.open(
'https://github.com/litmuschaos/litmus/tree/master/monitoring#model-1-optional-prometheus-scrape-config-model'
);
}}
>
<img
src="/icons/externalLink.svg"
alt="external link"
className={classes.linkIcon}
/>
</Icon>
</div>
</TextButton>
<div className={classes.horizontalLine} />
<Typography className={classes.heading}>
{t('analyticsDashboard.dataSourceForm.endPoint')}
@ -271,7 +261,7 @@ const ConfigurePrometheus: React.FC<ConfigurePrometheusProps> = ({
</div>
<div className={classes.inputDiv}>
<InputField
label={t('analyticsDashboard.dataSourceForm.Access')}
label={t('analyticsDashboard.dataSourceForm.access')}
data-cy="inputDataSourceAccess"
width="22.5rem"
variant={

View File

@ -8,7 +8,7 @@ const useStyles = makeStyles((theme) => ({
border: '1px',
borderRadius: '3px',
boxShadow:
'0px 0.3px 0.9px rgba(0, 0, 0, 0.1), 0px 1.6px 3.6px rgba(0, 0, 0, 0.13)',
'0 0.3px 0.9px rgba(0, 0, 0, 0.1), 0 1.6px 3.6px rgba(0, 0, 0, 0.13)',
padding: theme.spacing(4, 3.125, 6),
},
@ -63,20 +63,24 @@ const useStyles = makeStyles((theme) => ({
},
inlineIcon: {
margin: theme.spacing(0.5, 1, 0, 0),
width: '1rem',
height: '1rem',
margin: theme.spacing(0.25, 0),
width: '1.25rem',
height: '1.25rem',
},
linkIcon: {
margin: theme.spacing(0, 0, 0.45, 0.75),
width: '1rem',
height: '1rem',
},
iconWithTextDiv: {
display: 'flex',
button: {
minWidth: 0,
minHeight: 0,
padding: 0,
margin: theme.spacing(1.5, 0, 0, 2),
'&:hover': {
cursor: 'pointer !important',
},
},
buttonLabel: {
justifyContent: 'flex-start',
marginLeft: theme.spacing(0.5),
},
infoValue: {

View File

@ -1,21 +1,19 @@
import { ApolloError, useMutation } from '@apollo/client';
import { Typography } from '@material-ui/core';
import { ButtonFilled, ButtonOutlined, LightPills, Modal } from 'litmus-ui';
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import { ButtonFilled, LightPills, Modal, TextButton } from 'litmus-ui';
import moment from 'moment';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { DELETE_DATASOURCE } from '../../../../graphql';
import {
DeleteDataSourceInput,
deleteDSInput,
ListDataSourceResponse,
} from '../../../../models/graphql/dataSourceDetails';
import useActions from '../../../../redux/actions';
import * as DataSourceActions from '../../../../redux/actions/dataSource';
import * as TabActions from '../../../../redux/actions/tabs';
import { history } from '../../../../redux/configureStore';
import { ReactComponent as CogWheelIcon } from '../../../../svg/cogwheel.svg';
import { ReactComponent as CrossMarkIcon } from '../../../../svg/crossmark.svg';
import { ReactComponent as BinIcon } from '../../../../svg/delete.svg';
import { ReactComponent as ExternalLinkIcon } from '../../../../svg/externalLink.svg';
import {
getProjectID,
getProjectRole,
@ -24,45 +22,59 @@ import useStyles, { StyledTableCell } from './styles';
interface TableDataProps {
data: ListDataSourceResponse;
drawerStateHandler: (
ds_id: string,
ds_name: string,
dashboards: string[]
) => void;
alertStateHandler: (successState: boolean) => void;
}
const TableData: React.FC<TableDataProps> = ({ data }) => {
const TableData: React.FC<TableDataProps> = ({
data,
drawerStateHandler,
alertStateHandler,
}) => {
const classes = useStyles();
const { t } = useTranslation();
const projectID = getProjectID();
const projectRole = getProjectRole();
const dataSource = useActions(DataSourceActions);
const tabs = useActions(TabActions);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const [mutate, setMutate] = React.useState(false);
const [confirm, setConfirm] = React.useState(false);
const [success, setSuccess] = React.useState(false);
const [connectedDashboardsMessage, setConnectedDashboardsMessage] =
React.useState<string>('');
const [open, setOpen] = React.useState(false);
const [dataSourceSelectedForDeleting, setDataSourceSelectedForDeleting] =
React.useState<deleteDSInput>({
ds_id: '',
force_delete: false,
});
const [openModal, setOpenModal] = React.useState(false);
// Function to convert UNIX time in format of dddd, DD MMM YYYY, HH:mm
const formatDate = (date: string) => {
const updated = new Date(parseInt(date, 10) * 1000).toString();
const resDate = moment(updated).format('dddd, DD MMM YYYY, HH:mm');
return resDate;
};
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const [deleteDataSource] = useMutation<boolean, DeleteDataSourceInput>(
DELETE_DATASOURCE,
{
onCompleted: () => {
setConnectedDashboardsMessage('');
setSuccess(true);
setMutate(false);
setOpen(true);
alertStateHandler(true);
},
onError: (error: ApolloError) => {
setConnectedDashboardsMessage(error.message);
setMutate(false);
setOpen(true);
alertStateHandler(false);
if (error.message.includes('dashboard(s)')) {
const dashboardList: string = error.message.split(':')[1].trim();
const dashboardNames: string[] = dashboardList
.substring(1, dashboardList.length - 1)
.split(',');
drawerStateHandler(data.ds_id, data.ds_name, dashboardNames);
}
},
}
);
@ -70,7 +82,12 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
useEffect(() => {
if (mutate === true) {
deleteDataSource({
variables: { deleteDSInput: dataSourceSelectedForDeleting },
variables: {
deleteDSInput: {
ds_id: data.ds_id,
force_delete: false,
},
},
});
}
}, [mutate]);
@ -86,240 +103,177 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
<LightPills variant="danger" label={data.health_status} />
)}
</StyledTableCell>
<StyledTableCell className={classes.dataSourceName}>
<Typography variant="body2">
<strong>{data.ds_name}</strong>
<StyledTableCell className={classes.columnDivider}>
<Typography
className={`${classes.tableObjects} ${classes.dataSourceNameColData}`}
>
{data.ds_name}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.dataSourceType}>
<Typography variant="body2">
<strong>{data.ds_type}</strong>
<StyledTableCell className={classes.dividerPadding}>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '5rem' }}
>
<img
src="/icons/prometheus.svg"
alt="Prometheus"
className={classes.inlineIcon}
/>
{data.ds_type}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.dataSourceName}>
<Typography variant="body2">{formatDate(data.updated_at)}</Typography>
</StyledTableCell>
<StyledTableCell
onClick={() => {
dataSource.selectDataSource({
selectedDataSourceID: data.ds_id,
selectedDataSourceName: data.ds_name,
});
history.push({
pathname: '/analytics/datasource/configure',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
>
<Typography variant="body2" align="center">
<CogWheelIcon className={classes.cogWheelIcon} />
</Typography>
<Typography variant="body2" align="center">
Configure
<StyledTableCell>
<Typography
className={classes.tableObjects}
style={{ maxWidth: '12.5rem' }}
>
<img src="/icons/calendarIcon.svg" alt="Calender" />
{formatDate(data.updated_at)}
</Typography>
</StyledTableCell>
<StyledTableCell
onClick={() => {
const deleteDataSourceInput: deleteDSInput = {
force_delete: false,
ds_id: data.ds_id,
};
setDataSourceSelectedForDeleting(deleteDataSourceInput);
setOpen(true);
}}
>
<Typography variant="body2" align="center">
<BinIcon className={classes.binIcon} />
</Typography>
<Typography variant="body2" align="center" className={classes.delete}>
Delete
</Typography>
<StyledTableCell>
<TextButton
className={classes.button}
onClick={() => window.open(data.ds_url)}
endIcon={<ExternalLinkIcon className={classes.inlineIcon} />}
classes={{ label: classes.buttonLabel }}
>
<Typography
className={`${classes.tableObjects} ${classes.dataSourceUrlColData}`}
>
{data.ds_url}
</Typography>
</TextButton>
</StyledTableCell>
<StyledTableCell>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
data-cy="browseDataSourceOptions"
>
<MoreVertIcon className={classes.headerIcon} />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
getContentAnchorEl={null}
classes={{ paper: classes.menuList }}
>
<MenuItem
value="Configure"
onClick={() => {
dataSource.selectDataSource({
selectedDataSourceID: data.ds_id,
selectedDataSourceName: data.ds_name,
});
history.push({
pathname: '/analytics/datasource/configure',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
className={classes.menuItem}
>
<div className={classes.expDiv}>
<img
src="/icons/cogwheel.svg"
alt="Configure"
className={classes.btnImg}
/>
<Typography
data-cy="configureDashboard"
className={classes.btnText}
>
{t('analyticsDashboard.dataSourceTable.configure')}
</Typography>
</div>
</MenuItem>
<MenuItem
value="Delete"
onClick={() => {
setOpenModal(true);
handleClose();
}}
className={classes.menuItem}
>
<div className={classes.expDiv}>
<img
src="/icons/delete.svg"
alt="Delete"
className={classes.btnImg}
/>
<Typography
data-cy="deleteDashboard"
className={`${classes.btnText} ${classes.deleteText}`}
>
{t('analyticsDashboard.dataSourceTable.delete')}
</Typography>
</div>
</MenuItem>
</Menu>
</StyledTableCell>
<Modal
open={open}
onClose={() => {
setOpen(false);
setConfirm(false);
}}
width="60%"
modalActions={
<ButtonOutlined
className={classes.closeButton}
onClick={() => {
setOpen(false);
setConfirm(false);
}}
>
&#x2715;
</ButtonOutlined>
}
open={openModal}
onClose={() => setOpenModal(false)}
width="45%"
height="fit-content"
>
<div className={classes.modal}>
{confirm === true ? (
<Typography align="center">
{success === true ? (
<img
src="/icons/finish.svg"
alt="success"
className={classes.icon}
/>
) : (
<CrossMarkIcon className={classes.icon} />
)}
</Typography>
) : (
<div />
)}
<Typography className={classes.modalHeading} align="left">
{t('analyticsDashboard.dataSourceTable.modal.removeDataSource')}
</Typography>
{confirm === true ? (
<Typography
className={classes.modalHeading}
align="center"
variant="h3"
<Typography className={classes.modalBodyText} align="left">
{t(
'analyticsDashboard.dataSourceTable.modal.removeDataSourceConfirmation'
)}
<b>
<i>{` ${data.ds_name} `}</i>
</b>
?
</Typography>
<div
className={`${classes.flexButtons} ${classes.flexButtonsPadding}`}
>
<TextButton
onClick={() => setOpenModal(false)}
className={classes.cancelButton}
>
{success === true
? `The data source is successfully deleted.`
: success === false &&
dataSourceSelectedForDeleting?.force_delete === false
? `${connectedDashboardsMessage}`
: `There was a problem deleting your data source.`}
</Typography>
) : (
<div />
)}
{confirm === true ? (
<Typography
align="center"
variant="body1"
className={classes.modalBody}
>
{success === true ? (
<div>
You will see the data source deleted in the datasource table.
</div>
) : success === false &&
dataSourceSelectedForDeleting?.force_delete === false ? (
<div>
Dashboard(s) configured to use this data source will be
deleted.
</div>
) : (
<div>
Error encountered while deleting the data source. Please try
again.
</div>
)}
</Typography>
) : (
<div />
)}
{success === true && confirm === true ? (
<Typography className={classes.buttonText}>
{t('analyticsDashboard.dataSourceTable.modal.cancel')}
</Typography>
</TextButton>
<ButtonFilled
variant="success"
onClick={() => {
setConfirm(false);
setOpen(false);
tabs.changeAnalyticsDashboardTabs(3);
window.location.reload();
setMutate(true);
setOpenModal(false);
}}
variant="error"
>
<div>Back to Data Source</div>
<Typography
className={`${classes.buttonText} ${classes.confirmButtonText}`}
>
{t('analyticsDashboard.dataSourceTable.modal.delete')}
</Typography>
</ButtonFilled>
) : success === false && confirm === true ? (
<div className={classes.flexButtons}>
{dataSourceSelectedForDeleting?.force_delete === false ? (
<ButtonFilled
variant="error"
onClick={() => {
setDataSourceSelectedForDeleting({
ds_id: dataSourceSelectedForDeleting.ds_id,
force_delete: true,
});
setOpen(false);
setMutate(true);
}}
>
<div>Force Delete</div>
</ButtonFilled>
) : (
<ButtonOutlined
className={classes.buttonOutlineWarning}
onClick={() => {
setOpen(false);
setMutate(true);
}}
disabled={false}
>
<div>Try Again</div>
</ButtonOutlined>
)}
<ButtonFilled
onClick={() => {
setConfirm(false);
setOpen(false);
tabs.changeAnalyticsDashboardTabs(3);
window.location.reload();
}}
>
<div>Back to Data Source</div>
</ButtonFilled>
</div>
) : (
<div>
<Typography align="center">
<img
src="/icons/delete_large_icon.svg"
alt="delete"
className={classes.icon}
/>
</Typography>
<Typography
className={classes.modalHeading}
align="center"
variant="h3"
>
Are you sure to remove this data source?
</Typography>
<Typography
align="center"
variant="body1"
className={classes.modalBody}
>
The following action cannot be reverted.
</Typography>
<div className={classes.flexButtons}>
<ButtonOutlined
onClick={() => {
setOpen(false);
history.push({
pathname: '/analytics',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
disabled={false}
>
<div>No</div>
</ButtonOutlined>
<ButtonFilled
variant="error"
onClick={() => {
setConfirm(true);
setOpen(false);
setMutate(true);
}}
>
<div>Yes</div>
</ButtonFilled>
</div>
</div>
)}
</div>
</div>
</Modal>
</>

View File

@ -1,4 +1,4 @@
import { IconButton, TableHead, TableRow } from '@material-ui/core';
import { IconButton, TableHead, TableRow, Typography } from '@material-ui/core';
import ExpandLessTwoToneIcon from '@material-ui/icons/ExpandLessTwoTone';
import ExpandMoreTwoToneIcon from '@material-ui/icons/ExpandMoreTwoTone';
import React, { useEffect, useState } from 'react';
@ -8,8 +8,6 @@ import useStyles, { StyledTableCell } from './styles';
interface SortData {
lastConfigured: { sort: boolean; ascending: boolean };
name: { sort: boolean; ascending: boolean };
status: { sort: boolean; ascending: boolean };
dataSourceType: { sort: boolean; ascending: boolean };
}
interface SortCallBackType {
@ -27,8 +25,6 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
const [sortData, setSortData] = useState<SortData>({
name: { sort: false, ascending: true },
lastConfigured: { sort: true, ascending: false },
status: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
});
useEffect(() => {
@ -37,52 +33,22 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
return (
<TableHead>
<TableRow className={classes.tableHead}>
<TableRow>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.dataSourceStatusHead}>
<b>{t('analyticsDashboard.dataSourceTable.tableHead1')}</b>&nbsp;
</div>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort status ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastConfigured: { sort: false, ascending: true },
status: { sort: true, ascending: true },
dataSourceType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
</IconButton>
<IconButton
aria-label="sort status descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastConfigured: { sort: false, ascending: true },
status: { sort: true, ascending: false },
dataSourceType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
</IconButton>
</div>
</div>
<Typography
className={`${classes.dataSourceNameHead} ${classes.dataSourceStatusHeadWithoutSort}`}
>
{t('analyticsDashboard.dataSourceTable.tableHead1')}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.dataSourceName}>
<StyledTableCell
className={`${classes.headSpacing} ${classes.columnDivider}`}
>
<div className={classes.nameContent}>
<div className={classes.dataSourceNameHead}>
<b>{t('analyticsDashboard.dataSourceTable.tableHead2')}</b>&nbsp;
</div>
<Typography className={classes.dataSourceNameHead}>
{t('analyticsDashboard.dataSourceTable.tableHead2')}
</Typography>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort name ascending"
@ -92,12 +58,10 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: true, ascending: true },
lastConfigured: { sort: false, ascending: true },
status: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
<ExpandLessTwoToneIcon className={classes.markerIcon} />
</IconButton>
<IconButton
aria-label="sort name descending"
@ -107,62 +71,38 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: true, ascending: false },
lastConfigured: { sort: false, ascending: true },
status: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
<ExpandMoreTwoToneIcon className={classes.markerIcon} />
</IconButton>
</div>
</div>
</StyledTableCell>
<StyledTableCell
className={`${classes.headSpacing} ${classes.dividerPadding}`}
>
<Typography
className={`${classes.dataSourceNameHead} ${classes.dataSourceNameHeadWithoutSort}`}
>
{t('analyticsDashboard.dataSourceTable.tableHead3')}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<Typography
className={`${classes.dataSourceNameHead} ${classes.dataSourceNameHeadWithoutSort}`}
>
{t('analyticsDashboard.dataSourceTable.tableHead4')}
</Typography>
</StyledTableCell>
<StyledTableCell className={classes.headSpacing}>
<div className={classes.nameContent}>
<div className={classes.dataSourceNameHead}>
<b>{t('analyticsDashboard.dataSourceTable.tableHead3')}</b>&nbsp;
</div>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort datasource type ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastConfigured: { sort: false, ascending: true },
status: { sort: false, ascending: true },
dataSourceType: { sort: true, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
</IconButton>
<IconButton
aria-label="sort datasource type descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: false, ascending: true },
lastConfigured: { sort: false, ascending: true },
status: { sort: false, ascending: true },
dataSourceType: { sort: true, ascending: false },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
</IconButton>
</div>
</div>
</StyledTableCell>
<StyledTableCell className={classes.dataSourceName}>
<div className={classes.nameContent}>
<div className={classes.dataSourceNameHead}>
<b>{t('analyticsDashboard.dataSourceTable.tableHead4')}</b>&nbsp;
</div>
<Typography className={classes.dataSourceNameHead}>
{t('analyticsDashboard.dataSourceTable.tableHead5')}
</Typography>
<div className={classes.nameContentIcons}>
<IconButton
aria-label="sort last configured ascending"
@ -172,12 +112,10 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: false, ascending: true },
lastConfigured: { sort: true, ascending: true },
status: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
})
}
>
<ExpandLessTwoToneIcon className={classes.markerIconUp} />
<ExpandLessTwoToneIcon className={classes.markerIcon} />
</IconButton>
<IconButton
aria-label="sort last configured descending"
@ -187,18 +125,15 @@ const TableHeader: React.FC<TableHeaderProps> = ({ callBackToSort }) => {
...sortData,
name: { sort: false, ascending: true },
lastConfigured: { sort: true, ascending: false },
status: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
})
}
>
<ExpandMoreTwoToneIcon className={classes.markerIconDown} />
<ExpandMoreTwoToneIcon className={classes.markerIcon} />
</IconButton>
</div>
</div>
</StyledTableCell>
<StyledTableCell className={classes.options} />
<StyledTableCell className={classes.options} />
</TableRow>
</TableHead>
);

View File

@ -2,18 +2,15 @@ import {
Button,
FormControl,
IconButton,
InputAdornment,
InputBase,
InputLabel,
MenuItem,
OutlinedInput,
Select,
Typography,
} from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import SearchIcon from '@material-ui/icons/Search';
import { ButtonFilled } from 'litmus-ui';
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
import { ButtonFilled, Search } from 'litmus-ui';
import React, { ChangeEvent, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import DateRangeSelector from '../../../../components/DateRangeSelector';
@ -107,121 +104,151 @@ const TableToolBar: React.FC<TableToolBarProps> = ({
return (
<div className={classes.headerSection}>
<InputBase
id="input-with-icon-adornment"
placeholder="Search"
className={classes.search}
value={searchToken}
onChange={handleSearch}
classes={{
input: classes.input,
}}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
}
/>
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dataSourceStatusForm}`}
<div className={classes.search}>
<Search
id="input-with-icon-textfield"
placeholder={t('analyticsDashboard.dataSourceTable.search')}
value={searchToken}
onChange={handleSearch}
/>
</div>
<div
className={classes.headerSection}
style={{ justifyContent: 'flex-end' }}
>
<InputLabel className={classes.selectText}> Status </InputLabel>
<Select
label="Status"
value={status}
onChange={handleStatusChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dataSourceStatusForm}`}
>
<MenuItem value="All">All</MenuItem>
{statuses.map((status: string) => (
<MenuItem
key={`${status}-analyticsDashboard-dataSource-toolbar`}
value={status}
>
{status}
<InputLabel className={classes.selectText}>
{t('analyticsDashboard.dataSourceTable.tableHead1')}
</InputLabel>
<Select
label={t('analyticsDashboard.dataSourceTable.tableHead1')}
value={status}
onChange={handleStatusChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
IconComponent={KeyboardArrowDownIcon}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
}}
>
<MenuItem value="All" className={classes.menuListItem}>
{t('analyticsDashboard.dataSourceTable.all')}
</MenuItem>
))}
</Select>
</FormControl>
{statuses.map((availableStatus: string) => (
<MenuItem
key={`${availableStatus}-analyticsDashboard-dataSource-toolbar`}
value={availableStatus}
className={classes.menuListItem}
>
{availableStatus}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dataSourceNameForm}`}
>
<InputLabel className={classes.selectText}>
{' '}
Data Source Type{' '}
</InputLabel>
<Select
label="Data Source Type"
value={dataSourceType}
onChange={handleDataSourceTypeChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
<FormControl
variant="outlined"
className={`${classes.formControl} ${classes.dataSourceNameForm}`}
>
<MenuItem value="All">All</MenuItem>
{dataSourceTypes.map((dataSourceType: string) => (
<MenuItem
key={`${dataSourceType}-analyticsDashboard-dataSource-tableToolbar`}
value={dataSourceType}
>
{dataSourceType}
<InputLabel className={classes.selectText}>
{t('analyticsDashboard.dataSourceTable.tableHead3')}
</InputLabel>
<Select
label={t('analyticsDashboard.dataSourceTable.tableHead3')}
value={dataSourceType}
onChange={handleDataSourceTypeChange}
className={classes.selectText}
input={<OutlinedInput classes={outlinedInputClasses} />}
IconComponent={KeyboardArrowDownIcon}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
}}
>
<MenuItem value="All" className={classes.menuListItem}>
{t('analyticsDashboard.dataSourceTable.all')}
</MenuItem>
))}
</Select>
</FormControl>
{dataSourceTypes.map((availableDataSourceType: string) => (
<MenuItem
key={`${availableDataSourceType}-analyticsDashboard-dataSource-tableToolbar`}
value={availableDataSourceType}
className={classes.menuListItem}
>
{availableDataSourceType}
</MenuItem>
))}
</Select>
</FormControl>
<Button
className={classes.selectDate}
onClick={() => setDateRangeSelectorPopoverOpen(true)}
ref={dateRangeSelectorRef}
aria-label="time range"
aria-haspopup="true"
>
<Typography className={classes.displayDate}>
{range.startDate === ' '
? 'Select Period'
: `${range.startDate.split(' ')[2]} ${
range.startDate.split(' ')[1]
} ${range.startDate.split(' ')[3]} - ${
range.endDate.split(' ')[2]
} ${range.endDate.split(' ')[1]} ${range.endDate.split(' ')[3]}`}
<Button
className={`${classes.selectDate} ${
isDateRangeSelectorPopoverOpen ? classes.selectDateFocused : ''
}`}
onClick={() => setDateRangeSelectorPopoverOpen(true)}
ref={dateRangeSelectorRef}
aria-label="time range"
aria-haspopup="true"
>
<Typography className={classes.displayDate}>
{range.startDate === ' '
? t('analyticsDashboard.dataSourceTable.selectPeriod')
: `${range.startDate.split(' ')[2]} ${
range.startDate.split(' ')[1]
} ${range.startDate.split(' ')[3]} - ${
range.endDate.split(' ')[2]
} ${range.endDate.split(' ')[1]} ${
range.endDate.split(' ')[3]
}`}
<IconButton className={classes.rangeSelectorIcon}>
{isDateRangeSelectorPopoverOpen ? (
<KeyboardArrowDownIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</Typography>
</Button>
<div className={classes.addButton}>
<IconButton className={classes.rangeSelectorIcon}>
{isDateRangeSelectorPopoverOpen ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</Typography>
</Button>
<ButtonFilled
onClick={() => {
onClick={() =>
history.push({
pathname: '/analytics/datasource/create',
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
})
}
className={classes.addButton}
>
<Typography className={classes.dateRangeDefault}>
<Typography className={classes.buttonText}>
{t('analyticsDashboard.dataSourceTable.addDataSource')}
</Typography>
</ButtonFilled>
<DateRangeSelector
anchorEl={dateRangeSelectorRef.current as HTMLElement}
isOpen={isDateRangeSelectorPopoverOpen}
onClose={() => setDateRangeSelectorPopoverOpen(false)}
callbackToSetRange={CallbackFromRangeSelector}
/>
</div>
<DateRangeSelector
anchorEl={dateRangeSelectorRef.current as HTMLElement}
isOpen={isDateRangeSelectorPopoverOpen}
onClose={() => {
setDateRangeSelectorPopoverOpen(false);
}}
callbackToSetRange={CallbackFromRangeSelector}
/>
</div>
);
};

View File

@ -1,7 +1,9 @@
/* eslint-disable no-unused-expressions */
import { useQuery } from '@apollo/client';
import { useMutation, useQuery } from '@apollo/client';
import {
Drawer,
Paper,
Snackbar,
Table,
TableBody,
TableCell,
@ -10,13 +12,17 @@ import {
TableRow,
Typography,
} from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { ButtonFilled, ButtonOutlined, TextButton } from 'litmus-ui';
import moment from 'moment';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Loader from '../../../../components/Loader';
import { DELETE_DATASOURCE } from '../../../../graphql';
import { LIST_DATASOURCE } from '../../../../graphql/queries';
import {
DataSourceList,
DeleteDataSourceInput,
ListDataSourceResponse,
ListDataSourceVars,
} from '../../../../models/graphql/dataSourceDetails';
@ -40,8 +46,6 @@ interface RangeType {
interface SortData {
lastConfigured: { sort: boolean; ascending: boolean };
name: { sort: boolean; ascending: boolean };
status: { sort: boolean; ascending: boolean };
dataSourceType: { sort: boolean; ascending: boolean };
}
interface Filter {
@ -52,6 +56,12 @@ interface Filter {
searchTokens: string[];
}
interface ForceDeleteVars {
connectedDashboards: string[];
dsID: string;
dsName: string;
}
const DataSourceTable: React.FC = () => {
const classes = useStyles();
const { t } = useTranslation();
@ -61,8 +71,6 @@ const DataSourceTable: React.FC = () => {
sortData: {
name: { sort: false, ascending: true },
lastConfigured: { sort: true, ascending: false },
status: { sort: false, ascending: true },
dataSourceType: { sort: false, ascending: true },
},
selectedStatus: 'All',
searchTokens: [''],
@ -70,6 +78,17 @@ const DataSourceTable: React.FC = () => {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const projectID = getProjectID();
const [success, setSuccess] = React.useState(false);
const [isAlertOpen, setIsAlertOpen] = React.useState(false);
const [drawerState, setDrawerState] = React.useState(false);
const [showAllDashboards, setShowAllDashboards] = React.useState(false);
const [forceDeleteVars, setForceDeleteVars] = React.useState<ForceDeleteVars>(
{
connectedDashboards: [],
dsID: '',
dsName: '',
}
);
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
@ -83,15 +102,41 @@ const DataSourceTable: React.FC = () => {
};
// Apollo query to get the data source data
const { data, loading, error } = useQuery<DataSourceList, ListDataSourceVars>(
LIST_DATASOURCE,
const { data, loading, error, refetch } = useQuery<
DataSourceList,
ListDataSourceVars
>(LIST_DATASOURCE, {
variables: { projectID },
fetchPolicy: 'cache-and-network',
pollInterval: 10000,
});
const alertStateHandler = (successState: boolean) => {
setSuccess(successState);
setIsAlertOpen(true);
if (successState) {
refetch();
}
};
const [deleteDataSource] = useMutation<boolean, DeleteDataSourceInput>(
DELETE_DATASOURCE,
{
variables: { projectID },
fetchPolicy: 'cache-and-network',
pollInterval: 10000,
onCompleted: () => alertStateHandler(true),
onError: () => alertStateHandler(false),
}
);
const cleanDrawerState = () => {
setForceDeleteVars({
connectedDashboards: [],
dsID: '',
dsName: '',
});
setShowAllDashboards(false);
setDrawerState(false);
};
const getDataSourceType = (searchingData: ListDataSourceResponse[]) => {
const uniqueList: string[] = [];
searchingData.forEach((data) => {
@ -116,13 +161,8 @@ const DataSourceTable: React.FC = () => {
? !data.ListDataSource
? []
: data.ListDataSource.filter((ds: ListDataSourceResponse) => {
return filter.searchTokens.every(
(s: string) =>
ds.ds_name.toLowerCase().includes(s) ||
(ds.ds_type !== undefined &&
ds.ds_type.toLowerCase().includes(s)) ||
(ds.health_status !== undefined &&
ds.health_status.toLowerCase().includes(s))
return filter.searchTokens.every((s: string) =>
ds.ds_name.toLowerCase().includes(s)
);
})
.filter((data) => {
@ -167,21 +207,6 @@ const DataSourceTable: React.FC = () => {
? sortNumAsc(y, x)
: sortNumDesc(y, x);
}
if (filter.sortData.status.sort) {
const x = a.health_status;
const y = b.health_status;
return filter.sortData.status.ascending
? sortAlphaAsc(x, y)
: sortAlphaDesc(x, y);
}
if (filter.sortData.dataSourceType.sort) {
const x = a.ds_type;
const y = b.ds_type;
return filter.sortData.dataSourceType.ascending
? sortAlphaAsc(x, y)
: sortAlphaDesc(x, y);
}
return 0;
})
: [];
@ -207,46 +232,50 @@ const DataSourceTable: React.FC = () => {
.filter((s) => s !== ''),
})
}
dataSourceTypes={getDataSourceType(payload)}
statuses={getStatus(payload)}
callbackToSetDataSourceType={(dataSourceType: string) => {
dataSourceTypes={getDataSourceType(data?.ListDataSource ?? [])}
statuses={getStatus(data?.ListDataSource ?? [])}
callbackToSetDataSourceType={(dataSourceType: string) =>
setFilter({
...filter,
selectedDataSourceType: dataSourceType,
});
}}
callbackToSetStatus={(status: string) => {
})
}
callbackToSetStatus={(status: string) =>
setFilter({
...filter,
selectedStatus: status,
});
}}
})
}
callbackToSetRange={(
selectedStartDate: string,
selectedEndDate: string
) => {
) =>
setFilter({
...filter,
range: {
startDate: selectedStartDate,
endDate: selectedEndDate,
},
});
}}
})
}
/>
</section>
</Paper>
<Paper>
<section className="table section">
<TableContainer className={classes.tableMain}>
<TableContainer
className={`${classes.tableMain} ${
!payload.length || loading ? classes.minHeight : ''
}`}
>
<Table aria-label="simple table">
<TableHeader
callBackToSort={(sortConfigurations: SortData) => {
callBackToSort={(sortConfigurations: SortData) =>
setFilter({
...filter,
sortData: sortConfigurations,
});
}}
})
}
/>
<TableBody>
{error ? (
@ -260,18 +289,29 @@ const DataSourceTable: React.FC = () => {
) : loading ? (
<TableRow>
<TableCell colSpan={6}>
<Loader />
<Typography align="center">
{t('analyticsDashboard.dataSourceTable.loading')}
</Typography>
<div
className={`${classes.noRecords} ${classes.loading}`}
>
<Loader />
<Typography align="center">
{t('analyticsDashboard.dataSourceTable.loading')}
</Typography>
</div>
</TableCell>
</TableRow>
) : !payload.length ? (
<TableRow>
<TableCell colSpan={6}>
<Typography align="center">
{t('analyticsDashboard.dataSourceTable.noRecords')}
</Typography>
<div className={classes.noRecords}>
<img
src="/icons/dataSourceUnavailable.svg"
className={classes.unavailableIcon}
alt="Data Source"
/>
<Typography className={classes.noRecordsText}>
{t('analyticsDashboard.dataSourceTable.noRecords')}
</Typography>
</div>
</TableCell>
</TableRow>
) : payload.length > 0 ? (
@ -286,7 +326,22 @@ const DataSourceTable: React.FC = () => {
key={data.ds_id}
className={classes.tableRow}
>
<TableData data={data} />
<TableData
data={data}
drawerStateHandler={(
ds_id,
ds_name,
dashboards
) => {
setForceDeleteVars({
connectedDashboards: dashboards,
dsID: ds_id,
dsName: ds_name,
});
setDrawerState(true);
}}
alertStateHandler={alertStateHandler}
/>
</TableRow>
);
})
@ -311,9 +366,145 @@ const DataSourceTable: React.FC = () => {
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
className={classes.tablePagination}
SelectProps={{
MenuProps: {
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
getContentAnchorEl: null,
classes: { paper: classes.menuList },
},
}}
classes={{ menuItem: classes.menuListItem }}
/>
</section>
</Paper>
{isAlertOpen && (
<Snackbar
open={isAlertOpen}
autoHideDuration={3000}
onClose={() => setIsAlertOpen(false)}
>
<Alert
onClose={() => setIsAlertOpen(false)}
severity={success ? 'success' : 'error'}
>
{success
? t('analyticsDashboard.dataSourceTable.deletionSuccess')
: t('analyticsDashboard.dataSourceTable.deletionError')}
</Alert>
</Snackbar>
)}
<Drawer
className={classes.drawer}
variant="persistent"
anchor="right"
open={drawerState}
classes={{
paper: classes.drawerPaper,
}}
ModalProps={{
keepMounted: true,
}}
>
<div className={classes.drawerContent}>
<div className={classes.flexContainer}>
<Typography className={classes.drawerHeading} align="left">
{t('analyticsDashboard.dataSourceTable.delete')}
<b>
<i>{` ${forceDeleteVars.dsName} `}</i>
</b>
</Typography>
<ButtonOutlined
className={classes.closeButton}
onClick={() => cleanDrawerState()}
>
&#x2715;
</ButtonOutlined>
</div>
<blockquote className={classes.warningBlock}>
<Typography className={classes.warningText} align="left">
{t('analyticsDashboard.dataSourceTable.warning.text')}
</Typography>
</blockquote>
<Typography className={classes.drawerBodyText} align="left">
{t('analyticsDashboard.dataSourceTable.warning.info')}
</Typography>
<Typography
className={classes.drawerBodyText}
style={{ fontWeight: 500 }}
align="left"
>
{t(
'analyticsDashboard.dataSourceTable.warning.connectedDashboards'
)}
</Typography>
<div className={classes.dashboardsList}>
{(showAllDashboards
? forceDeleteVars.connectedDashboards
: forceDeleteVars.connectedDashboards.slice(0, 3)
).map((name: string, index: number) => (
<Typography
className={`${classes.drawerBodyText} ${classes.drawerListItem}`}
align="left"
key={`${name}-dashboard`}
>
{`${index + 1}. ${name}`}
</Typography>
))}
</div>
{forceDeleteVars.connectedDashboards.length - 3 >= 1 && (
<TextButton
onClick={() => setShowAllDashboards(!showAllDashboards)}
className={classes.cancelButton}
variant="highlight"
>
<Typography className={classes.buttonText}>
{showAllDashboards
? t('analyticsDashboard.dataSourceTable.warning.showLess')
: `+${forceDeleteVars.connectedDashboards.length - 3} ${t(
'analyticsDashboard.dataSourceTable.warning.dashboards'
)}`}
</Typography>
</TextButton>
)}
<div className={classes.flexButtons}>
<TextButton
onClick={() => cleanDrawerState()}
className={classes.cancelButton}
>
<Typography className={classes.buttonText}>
{t('analyticsDashboard.dataSourceTable.modal.cancel')}
</Typography>
</TextButton>
<ButtonFilled
onClick={() => {
deleteDataSource({
variables: {
deleteDSInput: {
ds_id: forceDeleteVars.dsID,
force_delete: true,
},
},
});
cleanDrawerState();
}}
variant="error"
>
<Typography
className={`${classes.buttonText} ${classes.confirmButtonText}`}
>
{t('analyticsDashboard.dataSourceTable.modal.forceDelete')}
</Typography>
</ButtonFilled>
</div>
</div>
</Drawer>
</div>
);
};

View File

@ -21,7 +21,8 @@ const useStyles = makeStyles((theme) => ({
flexDirection: 'row',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'space-between',
overflowX: 'auto',
height: '6rem',
backgroundColor: theme.palette.background.paper,
},
@ -30,40 +31,18 @@ const useStyles = makeStyles((theme) => ({
height: '4.5rem',
},
input: {
'&:-webkit-autofill': {
WebkitTextFillColor: theme.palette.text.primary,
WebkitBoxShadow: `0 0 0 1000px ${theme.palette.background.paper} inset`,
},
},
search: {
marginRight: 'auto',
marginLeft: theme.spacing(6.25),
borderBottom: `1px solid ${theme.palette.border.main}`,
},
binIcon: {
width: '1.5rem',
height: '1.5rem',
},
cogWheelIcon: {
width: '1.5rem',
height: '1.5rem',
},
delete: {
color: theme.palette.error.dark,
marginLeft: theme.spacing(6),
},
tableMain: {
marginTop: theme.spacing(4.25),
height: '30.5rem',
height: '29.75rem',
backgroundColor: theme.palette.background.paper,
minHeight: '25rem',
'&::-webkit-scrollbar': {
width: '0.2em',
height: '0.2em',
},
'&::-webkit-scrollbar-track': {
webkitBoxShadow: `inset 0 0 6px ${theme.palette.common.black}`,
@ -76,33 +55,58 @@ const useStyles = makeStyles((theme) => ({
},
},
tableHead: {
opacity: 0.7,
color: theme.palette.text.primary,
minHeight: {
height: '20rem',
minHeight: '20rem',
},
dataSourceName: {
borderRight: `1px solid ${theme.palette.border.main}`,
width: '12rem',
noRecords: {
height: '12.5rem',
display: 'flex',
padding: theme.spacing(7.5, 3, 5),
justifyContent: 'center',
},
dataSourceType: {
paddingLeft: theme.spacing(10.5),
width: '10rem',
loading: {
flexDirection: 'column',
padding: theme.spacing(2, 3, 7.5),
},
unavailableIcon: {
height: '3.5rem',
width: '3.5rem',
marginTop: theme.spacing(0.65),
},
noRecordsText: {
color: theme.palette.text.hint,
padding: theme.spacing(2),
fontSize: '1.5rem',
lineHeight: '150%',
letterSpacing: '0.1176px',
textAlign: 'center',
},
dataSourceNameHead: {
marginTop: theme.spacing(2.5),
fontWeight: 500,
fontSize: '0.75rem',
lineHeight: '150%',
color: theme.palette.text.hint,
},
dataSourceStatusHead: {
marginTop: theme.spacing(2.5),
paddingLeft: theme.spacing(2),
dataSourceNameHeadWithoutSort: {
marginTop: theme.spacing(0.75),
},
dataSourceStatusHeadWithoutSort: {
marginTop: theme.spacing(0.75),
paddingLeft: theme.spacing(4),
},
headSpacing: {
paddingLeft: theme.spacing(8),
maxWidth: '5rem',
minWidth: '5rem',
paddingLeft: theme.spacing(2),
},
nameContent: {
@ -116,25 +120,15 @@ const useStyles = makeStyles((theme) => ({
justifyContent: 'center',
},
markerIconDown: {
markerIcon: {
color: theme.palette.text.hint,
paddingTop: theme.spacing(0.5),
margin: 0,
},
markerIconUp: {
color: theme.palette.text.hint,
paddingTop: theme.spacing(0.5),
margin: 0,
},
tableDataStatus: {
paddingLeft: theme.spacing(9),
width: '8rem',
},
options: {
width: '5rem',
minWidth: '3rem',
paddingRight: theme.spacing(2),
},
tablePagination: {
@ -151,12 +145,19 @@ const useStyles = makeStyles((theme) => ({
selectDate: {
display: 'flex',
height: '2.9rem',
height: '2.75rem',
minWidth: '9rem',
border: `0.1px solid ${theme.palette.border.main}`,
borderRadius: 4,
borderRadius: '0.25rem',
marginRight: theme.spacing(1.5),
textTransform: 'none',
'&:hover': {
borderColor: theme.palette.highlight,
},
},
selectDateFocused: {
border: `2px solid ${theme.palette.highlight}`,
},
rangeSelectorIcon: {
@ -164,10 +165,63 @@ const useStyles = makeStyles((theme) => ({
height: '0.625rem',
},
tableDataStatus: {
paddingLeft: theme.spacing(6),
width: '5rem',
},
tableObjects: {
display: 'flex',
gap: '0.5rem',
textAlign: 'left',
color: theme.palette.text.primary,
fontSize: '0.75rem',
lineHeight: '150%',
},
dataSourceNameColData: {
maxWidth: '10rem',
fontWeight: 500,
},
dataSourceUrlColData: {
maxWidth: '8.5rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
inlineIcon: {
margin: theme.spacing(0.25, 0),
width: '1rem',
height: '1rem',
},
button: {
minWidth: 0,
minHeight: 0,
padding: 0,
width: 'fit-content',
'&:hover': {
cursor: 'pointer !important',
},
},
buttonLabel: {
justifyContent: 'flex-start',
marginLeft: theme.spacing(0.5),
},
columnDivider: {
borderRight: `1px solid ${theme.palette.border.main}`,
},
dividerPadding: {
paddingLeft: theme.spacing(4),
},
// Form Select Properties
formControl: {
margin: theme.spacing(0.5),
height: '2.8rem',
height: '2.6rem',
minWidth: '9rem',
},
@ -185,49 +239,151 @@ const useStyles = makeStyles((theme) => ({
padding: theme.spacing(0.4),
},
dateRangeDefault: {
height: '2rem',
textDecoration: 'none',
textTransform: 'none',
padding: theme.spacing(1),
},
addButton: {
marginRight: theme.spacing(3),
marginLeft: theme.spacing(1),
margin: theme.spacing(0, 3, 0, 1),
padding: theme.spacing(0, 2.5),
},
icon: {
width: '6rem',
height: '6rem',
// Menu option with icon
menuItem: {
width: '10rem',
height: '2.5rem',
'&:hover': {
background: theme.palette.cards.highlight,
},
},
modalHeading: {
marginTop: theme.spacing(3.5),
fontSize: '2.25rem',
marginBottom: theme.spacing(4.5),
headerIcon: {
color: theme.palette.border.main,
},
modalBody: {
marginBottom: theme.spacing(4.5),
},
closeButton: {
borderColor: theme.palette.border.main,
},
flexButtons: {
expDiv: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-evenly',
},
btnImg: {
width: '0.8125rem',
height: '0.8125rem',
marginTop: theme.spacing(0.375),
},
btnText: {
paddingLeft: theme.spacing(1.625),
},
deleteText: {
color: theme.palette.error.main,
},
buttonOutlineWarning: {
borderColor: theme.palette.error.dark,
// modal
modalHeading: {
fontSize: '1.5rem',
lineHeight: '130%',
fontWeight: 'bold',
fontFeatureSettings: 'pnum on, lnum on',
margin: theme.spacing(2.5, 0, 4.5),
padding: theme.spacing(0, 6.5),
},
modalBodyText: {
fontSize: '1rem',
lineHeight: '130%',
padding: theme.spacing(0, 6.5),
},
flexButtons: {
display: 'flex',
justifyContent: 'flex-end',
marginTop: 'auto',
},
flexButtonsPadding: {
padding: theme.spacing(5.5, 6.5, 0, 0),
},
modal: {
padding: theme.spacing(15, 0),
padding: theme.spacing(5, 0),
},
buttonText: {
lineHeight: '140%',
fontSize: '0.875rem',
},
confirmButtonText: {
color: theme.palette.text.secondary,
padding: theme.spacing(0, 3),
},
cancelButton: {
width: 'fit-content',
marginRight: theme.spacing(1.5),
padding: theme.spacing(0, 3),
},
// drawer
drawer: {
width: 'fit-content',
flexShrink: 0,
},
drawerPaper: {
width: '100%',
background: 'rgba(0, 0, 0, 0.6)',
},
drawerContent: {
height: '100%',
width: '50%',
marginLeft: '50%',
background: theme.palette.background.paper,
display: 'flex',
flexDirection: 'column',
padding: theme.spacing(10, 6.5),
},
closeButton: {
borderColor: theme.palette.border.main,
color: theme.palette.border.main,
padding: theme.spacing(0.25, 2),
minWidth: 0,
},
flexContainer: {
display: 'flex',
justifyContent: 'space-between',
},
drawerHeading: {
fontSize: '1.5rem',
lineHeight: '130%',
fontFeatureSettings: 'pnum on, lnum on',
},
warningBlock: {
margin: theme.spacing(5, 0),
padding: theme.spacing(0.5, 3),
borderLeft: `5px solid ${theme.palette.warning.main}`,
borderRadius: '5px 0 0 5px',
background: theme.palette.warning.light,
},
warningText: {
margin: theme.spacing(1.5, 0),
fontSize: '0.875rem',
lineHeight: '143%',
},
drawerBodyText: {
fontSize: '1rem',
lineHeight: '150%',
marginBottom: theme.spacing(3),
},
dashboardsList: {
maxHeight: '12rem',
overflowY: 'scroll',
marginBottom: theme.spacing(3),
},
drawerListItem: {
marginBottom: theme.spacing(2),
},
// select
menuList: {
boxShadow: '0 5px 9px rgba(0, 0, 0, 0.1)',
},
menuListItem: {
background: `${theme.palette.background.paper} !important`,
fontSize: '0.875rem',
lineHeight: '150%',
height: '1.875rem',
'&:hover': {
background: `${theme.palette.cards.highlight} !important`,
},
'&.Mui-selected': {
background: `${theme.palette.cards.highlight} !important`,
},
},
}));
@ -237,12 +393,12 @@ export const useOutlinedInputStyles = makeStyles((theme: Theme) => ({
borderColor: theme.palette.border.main,
},
'&:hover $notchedOutline': {
borderColor: theme.palette.border.main,
borderColor: theme.palette.highlight,
},
'&$focused $notchedOutline': {
borderColor: theme.palette.border.main,
borderColor: theme.palette.highlight,
},
height: '2.9rem',
height: '2.75rem',
},
focused: {},
notchedOutline: {},

View File

@ -184,7 +184,7 @@ const ApplicationDashboardCard: React.FC<ApplicationDashboardCardProps> = ({
>
<AnalyticsIcon />
</IconButton>
<Typography>Analytics</Typography>
<Typography align="center">View</Typography>
</div>
<div className={classes.cardActions}>
<IconButton
@ -201,13 +201,13 @@ const ApplicationDashboardCard: React.FC<ApplicationDashboardCardProps> = ({
>
<CogwheelIcon />
</IconButton>
<Typography>Configure</Typography>
<Typography align="center">Configure</Typography>
</div>
<div className={classes.cardActions}>
<IconButton onClick={() => downloadJSON()}>
<DownloadIcon />
</IconButton>
<Typography>JSON</Typography>
<Typography align="center">JSON</Typography>
</div>
</section>
</div>

View File

@ -9,7 +9,7 @@ const useStyles = makeStyles((theme) => ({
'&:hover': {
transform: `translateY(-10px)`,
boxShadow: `0px 1.2px 3.6px rgba(0, 0, 0, 0.1), 0px 6.4px 14.4px rgba(0, 0, 0, 0.13)`,
boxShadow: `0 1.2px 3.6px rgba(0, 0, 0, 0.1), 0 6.4px 14.4px rgba(0, 0, 0, 0.13)`,
borderRadius: '0.1875rem',
},
},

View File

@ -1,8 +1,11 @@
import { IconButton, Typography } from '@material-ui/core';
import React from 'react';
import { WorkflowRun } from '../../../../models/graphql/workflowData';
import useActions from '../../../../redux/actions';
import * as NodeSelectionActions from '../../../../redux/actions/nodeSelection';
import { history } from '../../../../redux/configureStore';
import { ReactComponent as AnalyticsIcon } from '../../../../svg/analytics.svg';
import { ReactComponent as WorkflowRunIcon } from '../../../../svg/workflowRun.svg';
import timeDifferenceForDate from '../../../../utils/datesModifier';
import {
getProjectID,
@ -25,9 +28,9 @@ const WorkflowDashboardCard: React.FC<WorkflowDashboardCardProps> = ({
data,
}) => {
const classes = useStyles();
const projectID = getProjectID();
const projectRole = getProjectRole();
const nodeSelection = useActions(NodeSelectionActions);
function getStatusVariant(phase: string) {
switch (phase) {
@ -73,6 +76,23 @@ const WorkflowDashboardCard: React.FC<WorkflowDashboardCardProps> = ({
{timeDifferenceForDate(data.last_updated)}
</Typography>
<section className={classes.cardActionsSection}>
<div className={classes.cardActions}>
<IconButton
onClick={() => {
nodeSelection.selectNode({
pod_name: '',
});
if (data.phase?.toLowerCase() !== 'notavailable')
history.push({
pathname: `/workflows/${data.workflow_run_id}`,
search: `?projectID=${projectID}&projectRole=${projectRole}`,
});
}}
>
<WorkflowRunIcon />
</IconButton>
<Typography align="center">See workflow run</Typography>
</div>
<div className={classes.cardActions}>
<IconButton
onClick={() => {
@ -84,7 +104,7 @@ const WorkflowDashboardCard: React.FC<WorkflowDashboardCardProps> = ({
>
<AnalyticsIcon />
</IconButton>
<Typography>See Analytics</Typography>
<Typography align="center">See analytics</Typography>
</div>
</section>
</div>

View File

@ -9,7 +9,7 @@ const useStyles = makeStyles((theme) => ({
'&:hover': {
transform: `translateY(-10px)`,
boxShadow: `0px 1.2px 3.6px rgba(0, 0, 0, 0.1), 0px 6.4px 14.4px rgba(0, 0, 0, 0.13)`,
boxShadow: `0 1.2px 3.6px rgba(0, 0, 0, 0.1), 0 6.4px 14.4px rgba(0, 0, 0, 0.13)`,
borderRadius: '0.1875rem',
},
},

View File

@ -242,7 +242,7 @@ const Overview: React.FC = () => {
src="./icons/dashboardCloud.svg"
alt="Schedule a workflow"
heading="Configure a chaos interleaved dashboard"
description="Data source(s) have been found to be connected in this project. Select “Add dashboard” to configure a chaos interleaved dashboard"
description="Data source(s) have been found to be connected in this project. Select “Create dashboard” to configure a chaos interleaved dashboard"
button={
<ButtonFilled
onClick={() => {
@ -252,14 +252,14 @@ const Overview: React.FC = () => {
});
}}
>
<Typography>Add dashboard</Typography>
<Typography>Create dashboard</Typography>
</ButtonFilled>
}
/>
)}{' '}
{workflowDashboardCount > 0 ? (
<RecentOverviewContainer
heading="Recent Workflow Dashboards"
heading="Recently updated workflow dashboards"
buttonLink="/create-workflow"
buttonImgSrc="./icons/calendarBlank.svg"
buttonImgAlt="Schedule workflow"
@ -308,11 +308,11 @@ const Overview: React.FC = () => {
)}
{applicationDashboardCount > 0 && (
<RecentOverviewContainer
heading="Recent Application Dashboards"
heading="Recently viewed application dashboards"
buttonLink="/analytics/dashboard/create"
buttonImgSrc="./icons/cloudWhite.svg"
buttonImgAlt="Add dashboard"
buttonText="Add dashbaord"
buttonImgAlt="Create dashboard"
buttonText="Create dashbaord"
>
{dashboardListLoading ? (
<Center>