Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,21 +588,33 @@ func unifiedKindCompare(a, b ResourceWithLabels, isDesc bool) bool {

func unifiedNameCompare(a ResourceWithLabels, b ResourceWithLabels, isDesc bool) bool {
var nameA, nameB string
resourceA, ok := a.(Server)
if ok {
nameA = resourceA.GetHostname()
} else {
switch r := a.(type) {
case AppServer:
nameA = r.GetApp().GetName()
case DatabaseServer:
nameA = r.GetDatabase().GetName()
case KubeServer:
nameA = r.GetCluster().GetName()
case Server:
nameA = r.GetHostname()
default:
nameA = a.GetName()
}

resourceB, ok := b.(Server)
if ok {
nameB = resourceB.GetHostname()
} else {
nameB = b.GetName()
switch r := b.(type) {
case AppServer:
nameB = r.GetApp().GetName()
case DatabaseServer:
nameB = r.GetDatabase().GetName()
case KubeServer:
nameB = r.GetCluster().GetName()
case Server:
nameB = r.GetHostname()
default:
nameB = a.GetName()
}

return stringCompare(nameA, nameB, isDesc)
return stringCompare(strings.ToLower(nameA), strings.ToLower(nameB), isDesc)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I believe I have the test testing what we want 91cb348

}

func (r ResourcesWithLabels) SortByCustom(by SortBy) error {
Expand Down
77 changes: 77 additions & 0 deletions api/types/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,83 @@ func TestMatchSearch(t *testing.T) {
}
}

func TestUnifiedNameCompare(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
resourceA func(*testing.T) ResourceWithLabels
resourceB func(*testing.T) ResourceWithLabels
isDesc bool
expect bool
}{
{
name: "sort by same kind",
resourceA: func(t *testing.T) ResourceWithLabels {
server, err := NewServer("node-cloud", KindNode, ServerSpecV2{
Hostname: "node-cloud",
})
require.NoError(t, err)
return server
},
resourceB: func(t *testing.T) ResourceWithLabels {
server, err := NewServer("node-strawberry", KindNode, ServerSpecV2{
Hostname: "node-strawberry",
})
require.NoError(t, err)
return server
},
isDesc: true,
expect: false,
},
{
name: "sort by different kind",
resourceA: func(t *testing.T) ResourceWithLabels {
server := newAppServer(t, "app-cloud")
return server
},
resourceB: func(t *testing.T) ResourceWithLabels {
server, err := NewServer("node-strawberry", KindNode, ServerSpecV2{
Hostname: "node-strawberry",
})
require.NoError(t, err)
return server
},
isDesc: true,
expect: false,
},
{
name: "sort with different cases",
resourceA: func(t *testing.T) ResourceWithLabels {
server := newAppServer(t, "app-cloud")
return server
},
resourceB: func(t *testing.T) ResourceWithLabels {
server, err := NewServer("Node-strawberry", KindNode, ServerSpecV2{
Hostname: "node-strawberry",
})
require.NoError(t, err)
return server
},
isDesc: true,
expect: false,
},
}

for _, tc := range testCases {
tc := tc
resourceA := tc.resourceA(t)
resourceB := tc.resourceB(t)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

actual := unifiedNameCompare(resourceA, resourceB, tc.isDesc)
if actual != tc.expect {
t.Errorf("Expected %v, but got %v for %+v and %+v with isDesc=%v", tc.expect, actual, resourceA, resourceB, tc.isDesc)
}
})
}
}

func TestMatchSearch_ResourceSpecific(t *testing.T) {
t.Parallel()

Expand Down
47 changes: 46 additions & 1 deletion web/packages/teleport/src/UnifiedResources/FilterPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import React, { useState } from 'react';
import styled from 'styled-components';
import { ButtonBorder, ButtonPrimary, ButtonSecondary } from 'design/Button';
import { SortDir } from 'design/DataTable/types';
import { Text } from 'design';
Expand Down Expand Up @@ -151,6 +152,14 @@ const FilterTypesMenu = ({
setKinds(newKinds);
};

const handleSelectAll = () => {
setKinds(kindOptions.map(k => k.value));
};

const handleClearAll = () => {
setKinds([]);
};

Comment on lines +155 to +162
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods seem a bit simple atm, but when we start to add more filter types besides just kinds they will grow a bit.

const applyFilters = () => {
onChange(kinds);
handleClose();
Expand All @@ -167,8 +176,9 @@ const FilterTypesMenu = ({
size="small"
onClick={handleOpen}
>
Type
Types {kindsFromParams.length > 0 ? `(${kindsFromParams.length})` : ''}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, this will change once we add more filters but abstracting now seemed a bit frivolous

<ChevronDown ml={2} size="small" color="text.slightlyMuted" />
{kindsFromParams.length > 0 && <FiltersExistIndicator />}
</ButtonSecondary>
<Menu
popoverCss={() => `margin-top: 36px;`}
Expand All @@ -184,6 +194,30 @@ const FilterTypesMenu = ({
open={Boolean(anchorEl)}
onClose={cancelUpdate}
>
<Flex gap={2} p={2}>
<ButtonSecondary
size="small"
onClick={handleSelectAll}
textTransform="none"
css={`
background-color: transparent;
`}
px={2}
>
Select All
</ButtonSecondary>
<ButtonSecondary
size="small"
onClick={handleClearAll}
textTransform="none"
css={`
background-color: transparent;
`}
px={2}
>
Clear All
</ButtonSecondary>
</Flex>
{kindOptions.map(kind => (
<MenuItem
px={2}
Expand Down Expand Up @@ -320,3 +354,14 @@ function kindArraysEqual(arr1: string[], arr2: string[]) {

return true;
}

const FiltersExistIndicator = styled.div`
position: absolute;
top: -4px;
right: -4px;
height: 12px;
width: 12px;
background-color: ${props => props.theme.colors.brand};
border-radius: 50%;
display: inline-block;
`;
19 changes: 1 addition & 18 deletions web/packages/teleport/src/UnifiedResources/Resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,7 @@ limitations under the License.
import React, { useEffect, useState } from 'react';

import styled from 'styled-components';
import {
Box,
Indicator,
Flex,
ButtonLink,
ButtonSecondary,
Text,
} from 'design';
import { Box, Flex, ButtonLink, ButtonSecondary, Text } from 'design';
import { Magnifier } from 'design/Icon';

import { Danger } from 'design/Alert';
Expand Down Expand Up @@ -171,9 +164,6 @@ export function Resources() {
</ResourcesContainer>
<div ref={setScrollDetector} />
<ListFooter>
<IndicatorContainer status={attempt.status}>
<Indicator size={INDICATOR_SIZE} />
</IndicatorContainer>
{attempt.status === 'failed' && resources.length > 0 && (
<ButtonSecondary onClick={onRetryClicked}>Load more</ButtonSecondary>
)}
Expand Down Expand Up @@ -253,13 +243,6 @@ const ListFooter = styled.div`
text-align: center;
`;

// Line height is set to 0 to prevent the layout engine from adding extra pixels
// to the element's height.
const IndicatorContainer = styled(Box)`
display: ${props => (props.status === 'processing' ? 'block' : 'none')};
line-height: 0;
`;

const emptyStateInfo: EmptyStateInfo = {
title: 'Add your first resource to Teleport',
byline:
Expand Down
5 changes: 4 additions & 1 deletion web/packages/teleport/src/services/apps/makeApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,28 +84,31 @@ export default function makeApp(json: any): App {
}

function guessAppIcon(json: any): GuessedAppType {
const { name, labels, awsConsole = false } = json;
const { name, labels, friendlyName, awsConsole = false } = json;

if (awsConsole) {
return 'Aws';
}

if (
name?.toLocaleLowerCase().includes('slack') ||
friendlyName?.toLocaleLowerCase().includes('slack') ||
labels?.some(l => `${l.name}:${l.value}` === 'icon:slack')
) {
return 'Slack';
}

if (
name?.toLocaleLowerCase().includes('grafana') ||
friendlyName?.toLocaleLowerCase().includes('grafana') ||
labels?.some(l => `${l.name}:${l.value}` === 'icon:grafana')
) {
return 'Grafana';
}

if (
name?.toLocaleLowerCase().includes('jenkins') ||
friendlyName?.toLocaleLowerCase().includes('jenkins') ||
labels?.some(l => `${l.name}:${l.value}` === 'icon:jenkins')
) {
return 'Jenkins';
Expand Down