Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
02e7d713
Commit
02e7d713
authored
Oct 11, 2018
by
Peter Holmberg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added Loading state on org pages
parent
974eddee
Show whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
242 additions
and
164 deletions
+242
-164
public/app/core/components/PageLoader/PageLoader.tsx
+17
-0
public/app/features/api-keys/ApiKeysPage.test.tsx
+2
-0
public/app/features/api-keys/ApiKeysPage.tsx
+41
-29
public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap
+4
-25
public/app/features/api-keys/state/reducers.ts
+2
-1
public/app/features/api-keys/state/selectors.test.ts
+2
-2
public/app/features/datasources/DataSourcesListPage.test.tsx
+2
-0
public/app/features/datasources/DataSourcesListPage.tsx
+9
-6
public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap
+2
-13
public/app/features/datasources/state/reducers.ts
+2
-1
public/app/features/plugins/PluginListPage.test.tsx
+11
-8
public/app/features/plugins/PluginListPage.tsx
+18
-2
public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap
+27
-0
public/app/features/plugins/state/reducers.ts
+2
-1
public/app/features/teams/TeamList.test.tsx
+2
-0
public/app/features/teams/TeamList.tsx
+15
-2
public/app/features/teams/__snapshots__/TeamList.test.tsx.snap
+2
-17
public/app/features/teams/state/reducers.ts
+2
-2
public/app/features/teams/state/selectors.test.ts
+2
-2
public/app/features/users/UsersListPage.test.tsx
+9
-0
public/app/features/users/UsersListPage.tsx
+22
-11
public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap
+20
-1
public/app/features/users/state/reducers.ts
+4
-3
public/app/types/apiKeys.ts
+1
-0
public/app/types/datasources.ts
+1
-0
public/app/types/plugins.ts
+1
-0
public/app/types/teams.ts
+1
-0
public/app/types/user.ts
+1
-0
public/app/types/users.ts
+0
-37
public/sass/_grafana.scss
+2
-1
public/sass/components/_page_loader.scss
+16
-0
No files found.
public/app/core/components/PageLoader/PageLoader.tsx
0 → 100644
View file @
02e7d713
import
React
,
{
SFC
}
from
'react'
;
interface
Props
{
pageName
:
string
;
}
const
PageLoader
:
SFC
<
Props
>
=
({
pageName
})
=>
{
const
loadingText
=
`Loading
${
pageName
}
...`
;
return
(
<
div
className=
"page-loader-wrapper"
>
<
i
className=
"page-loader-wrapper__spinner fa fa-spinner fa-spin"
/>
<
div
className=
"page-loader-wrapper__text"
>
{
loadingText
}
</
div
>
</
div
>
);
};
export
default
PageLoader
;
public/app/features/api-keys/ApiKeysPage.test.tsx
View file @
02e7d713
...
...
@@ -9,6 +9,7 @@ const setup = (propOverrides?: object) => {
navModel
:
{}
as
NavModel
,
apiKeys
:
[]
as
ApiKey
[],
searchQuery
:
''
,
hasFetched
:
false
,
loadApiKeys
:
jest
.
fn
(),
deleteApiKey
:
jest
.
fn
(),
setSearchQuery
:
jest
.
fn
(),
...
...
@@ -35,6 +36,7 @@ describe('Render', () => {
it
(
'should render API keys table'
,
()
=>
{
const
{
wrapper
}
=
setup
({
apiKeys
:
getMultipleMockKeys
(
5
),
hasFetched
:
true
,
});
expect
(
wrapper
).
toMatchSnapshot
();
...
...
public/app/features/api-keys/ApiKeysPage.tsx
View file @
02e7d713
...
...
@@ -8,6 +8,7 @@ import { getApiKeys } from './state/selectors';
import
{
loadApiKeys
,
deleteApiKey
,
setSearchQuery
,
addApiKey
}
from
'./state/actions'
;
import
PageHeader
from
'app/core/components/PageHeader/PageHeader'
;
import
SlideDown
from
'app/core/components/Animations/SlideDown'
;
import
PageLoader
from
'app/core/components/PageLoader/PageLoader'
;
import
ApiKeysAddedModal
from
'./ApiKeysAddedModal'
;
import
config
from
'app/core/config'
;
import
appEvents
from
'app/core/app_events'
;
...
...
@@ -16,6 +17,7 @@ export interface Props {
navModel
:
NavModel
;
apiKeys
:
ApiKey
[];
searchQuery
:
string
;
hasFetched
:
boolean
;
loadApiKeys
:
typeof
loadApiKeys
;
deleteApiKey
:
typeof
deleteApiKey
;
setSearchQuery
:
typeof
setSearchQuery
;
...
...
@@ -99,9 +101,45 @@ export class ApiKeysPage extends PureComponent<Props, any> {
});
};
renderTable
()
{
const
{
apiKeys
}
=
this
.
props
;
return
[
<
h3
key=
"header"
className=
"page-heading"
>
Existing Keys
</
h3
>,
<
table
key=
"table"
className=
"filter-table"
>
<
thead
>
<
tr
>
<
th
>
Name
</
th
>
<
th
>
Role
</
th
>
<
th
style=
{
{
width
:
'34px'
}
}
/>
</
tr
>
</
thead
>
{
apiKeys
.
length
>
0
&&
(
<
tbody
>
{
apiKeys
.
map
(
key
=>
{
return
(
<
tr
key=
{
key
.
id
}
>
<
td
>
{
key
.
name
}
</
td
>
<
td
>
{
key
.
role
}
</
td
>
<
td
>
<
a
onClick=
{
()
=>
this
.
onDeleteApiKey
(
key
)
}
className=
"btn btn-danger btn-mini"
>
<
i
className=
"fa fa-remove"
/>
</
a
>
</
td
>
</
tr
>
);
})
}
</
tbody
>
)
}
</
table
>,
];
}
render
()
{
const
{
newApiKey
,
isAdding
}
=
this
.
state
;
const
{
navModel
,
apiKeys
,
searchQuery
}
=
this
.
props
;
const
{
hasFetched
,
navModel
,
searchQuery
}
=
this
.
props
;
return
(
<
div
>
...
...
@@ -170,34 +208,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
</
form
>
</
div
>
</
SlideDown
>
<
h3
className=
"page-heading"
>
Existing Keys
</
h3
>
<
table
className=
"filter-table"
>
<
thead
>
<
tr
>
<
th
>
Name
</
th
>
<
th
>
Role
</
th
>
<
th
style=
{
{
width
:
'34px'
}
}
/>
</
tr
>
</
thead
>
{
apiKeys
.
length
>
0
?
(
<
tbody
>
{
apiKeys
.
map
(
key
=>
{
return
(
<
tr
key=
{
key
.
id
}
>
<
td
>
{
key
.
name
}
</
td
>
<
td
>
{
key
.
role
}
</
td
>
<
td
>
<
a
onClick=
{
()
=>
this
.
onDeleteApiKey
(
key
)
}
className=
"btn btn-danger btn-mini"
>
<
i
className=
"fa fa-remove"
/>
</
a
>
</
td
>
</
tr
>
);
})
}
</
tbody
>
)
:
null
}
</
table
>
{
hasFetched
?
this
.
renderTable
()
:
<
PageLoader
pageName=
"Api keys"
/>
}
</
div
>
</
div
>
);
...
...
@@ -209,6 +220,7 @@ function mapStateToProps(state) {
navModel
:
getNavModel
(
state
.
navIndex
,
'apikeys'
),
apiKeys
:
getApiKeys
(
state
.
apiKeys
),
searchQuery
:
state
.
apiKeys
.
searchQuery
,
hasFetched
:
state
.
apiKeys
.
hasFetched
,
};
}
...
...
public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap
View file @
02e7d713
...
...
@@ -138,11 +138,13 @@ exports[`Render should render API keys table 1`] = `
</Component>
<h3
className="page-heading"
key="header"
>
Existing Keys
</h3>
<table
className="filter-table"
key="table"
>
<thead>
<tr>
...
...
@@ -404,32 +406,9 @@ exports[`Render should render component 1`] = `
</form>
</div>
</Component>
<h3
className="page-heading"
>
Existing Keys
</h3>
<table
className="filter-table"
>
<thead>
<tr>
<th>
Name
</th>
<th>
Role
</th>
<th
style={
Object {
"width": "34px",
}
}
<PageLoader
pageName="Api keys"
/>
</tr>
</thead>
</table>
</div>
</div>
`;
public/app/features/api-keys/state/reducers.ts
View file @
02e7d713
...
...
@@ -4,12 +4,13 @@ import { Action, ActionTypes } from './actions';
export
const
initialApiKeysState
:
ApiKeysState
=
{
keys
:
[],
searchQuery
:
''
,
hasFetched
:
false
,
};
export
const
apiKeysReducer
=
(
state
=
initialApiKeysState
,
action
:
Action
):
ApiKeysState
=>
{
switch
(
action
.
type
)
{
case
ActionTypes
.
LoadApiKeys
:
return
{
...
state
,
keys
:
action
.
payload
};
return
{
...
state
,
hasFetched
:
true
,
keys
:
action
.
payload
};
case
ActionTypes
.
SetApiKeysSearchQuery
:
return
{
...
state
,
searchQuery
:
action
.
payload
};
}
...
...
public/app/features/api-keys/state/selectors.test.ts
View file @
02e7d713
...
...
@@ -7,7 +7,7 @@ describe('API Keys selectors', () => {
const
mockKeys
=
getMultipleMockKeys
(
5
);
it
(
'should return all keys if no search query'
,
()
=>
{
const
mockState
:
ApiKeysState
=
{
keys
:
mockKeys
,
searchQuery
:
''
};
const
mockState
:
ApiKeysState
=
{
keys
:
mockKeys
,
searchQuery
:
''
,
hasFetched
:
false
};
const
keys
=
getApiKeys
(
mockState
);
...
...
@@ -15,7 +15,7 @@ describe('API Keys selectors', () => {
});
it
(
'should filter keys if search query exists'
,
()
=>
{
const
mockState
:
ApiKeysState
=
{
keys
:
mockKeys
,
searchQuery
:
'5'
};
const
mockState
:
ApiKeysState
=
{
keys
:
mockKeys
,
searchQuery
:
'5'
,
hasFetched
:
false
};
const
keys
=
getApiKeys
(
mockState
);
...
...
public/app/features/datasources/DataSourcesListPage.test.tsx
View file @
02e7d713
...
...
@@ -15,6 +15,7 @@ const setup = (propOverrides?: object) => {
searchQuery
:
''
,
setDataSourcesSearchQuery
:
jest
.
fn
(),
setDataSourcesLayoutMode
:
jest
.
fn
(),
hasFetched
:
false
,
};
Object
.
assign
(
props
,
propOverrides
);
...
...
@@ -33,6 +34,7 @@ describe('Render', () => {
const
wrapper
=
setup
({
dataSources
:
getMockDataSources
(
5
),
dataSourcesCount
:
5
,
hasFetched
:
true
,
});
expect
(
wrapper
).
toMatchSnapshot
();
...
...
public/app/features/datasources/DataSourcesListPage.tsx
View file @
02e7d713
...
...
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
import
{
connect
}
from
'react-redux'
;
import
{
hot
}
from
'react-hot-loader'
;
import
PageHeader
from
'../../core/components/PageHeader/PageHeader'
;
import
PageLoader
from
'app/core/components/PageLoader/PageLoader'
;
import
OrgActionBar
from
'../../core/components/OrgActionBar/OrgActionBar'
;
import
EmptyListCTA
from
'../../core/components/EmptyListCTA/EmptyListCTA'
;
import
DataSourcesList
from
'./DataSourcesList'
;
...
...
@@ -22,6 +23,7 @@ export interface Props {
dataSourcesCount
:
number
;
layoutMode
:
LayoutMode
;
searchQuery
:
string
;
hasFetched
:
boolean
;
loadDataSources
:
typeof
loadDataSources
;
setDataSourcesLayoutMode
:
typeof
setDataSourcesLayoutMode
;
setDataSourcesSearchQuery
:
typeof
setDataSourcesSearchQuery
;
...
...
@@ -56,6 +58,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
searchQuery
,
setDataSourcesSearchQuery
,
setDataSourcesLayoutMode
,
hasFetched
,
}
=
this
.
props
;
const
linkButton
=
{
...
...
@@ -67,10 +70,10 @@ export class DataSourcesListPage extends PureComponent<Props> {
<
div
>
<
PageHeader
model=
{
navModel
}
/>
<
div
className=
"page-container page-body"
>
{
dataSourcesCount
===
0
?
(
<
EmptyListCTA
model=
{
emptyListModel
}
/>
)
:
(
[
{
!
hasFetched
&&
<
PageLoader
pageName=
"Data sources"
/>
}
{
hasFetched
&&
dataSourcesCount
===
0
&&
<
EmptyListCTA
model=
{
emptyListModel
}
/>
}
{
hasFetched
&&
dataSourcesCount
>
0
&&
[
<
OrgActionBar
layoutMode=
{
layoutMode
}
searchQuery=
{
searchQuery
}
...
...
@@ -80,8 +83,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
key=
"action-bar"
/>,
<
DataSourcesList
dataSources=
{
dataSources
}
layoutMode=
{
layoutMode
}
key=
"list"
/>,
]
)
}
]
}
</
div
>
</
div
>
);
...
...
@@ -95,6 +97,7 @@ function mapStateToProps(state) {
layoutMode
:
getDataSourcesLayoutMode
(
state
.
dataSources
),
dataSourcesCount
:
getDataSourcesCount
(
state
.
dataSources
),
searchQuery
:
getDataSourcesSearchQuery
(
state
.
dataSources
),
hasFetched
:
state
.
dataSources
.
hasFetched
,
};
}
...
...
public/app/features/datasources/__snapshots__/DataSourcesListPage.test.tsx.snap
View file @
02e7d713
...
...
@@ -155,19 +155,8 @@ exports[`Render should render component 1`] = `
<div
className="page-container page-body"
>
<EmptyListCTA
model={
Object {
"buttonIcon": "gicon gicon-add-datasources",
"buttonLink": "datasources/new",
"buttonTitle": "Add data source",
"proTip": "You can also define data sources through configuration files.",
"proTipLink": "http://docs.grafana.org/administration/provisioning/#datasources?utm_source=grafana_ds_list",
"proTipLinkTitle": "Learn more",
"proTipTarget": "_blank",
"title": "There are no data sources defined yet",
}
}
<PageLoader
pageName="Data sources"
/>
</div>
</div>
...
...
public/app/features/datasources/state/reducers.ts
View file @
02e7d713
...
...
@@ -9,12 +9,13 @@ const initialState: DataSourcesState = {
dataSourcesCount
:
0
,
dataSourceTypes
:
[]
as
Plugin
[],
dataSourceTypeSearchQuery
:
''
,
hasFetched
:
false
,
};
export
const
dataSourcesReducer
=
(
state
=
initialState
,
action
:
Action
):
DataSourcesState
=>
{
switch
(
action
.
type
)
{
case
ActionTypes
.
LoadDataSources
:
return
{
...
state
,
dataSources
:
action
.
payload
,
dataSourcesCount
:
action
.
payload
.
length
};
return
{
...
state
,
hasFetched
:
true
,
dataSources
:
action
.
payload
,
dataSourcesCount
:
action
.
payload
.
length
};
case
ActionTypes
.
SetDataSourcesSearchQuery
:
return
{
...
state
,
searchQuery
:
action
.
payload
};
...
...
public/app/features/plugins/PluginListPage.test.tsx
View file @
02e7d713
...
...
@@ -13,22 +13,25 @@ const setup = (propOverrides?: object) => {
setPluginsLayoutMode
:
jest
.
fn
(),
layoutMode
:
LayoutModes
.
Grid
,
loadPlugins
:
jest
.
fn
(),
hasFetched
:
false
,
};
Object
.
assign
(
props
,
propOverrides
);
const
wrapper
=
shallow
(<
PluginListPage
{
...
props
}
/>);
const
instance
=
wrapper
.
instance
()
as
PluginListPage
;
return
{
wrapper
,
instance
,
};
return
shallow
(<
PluginListPage
{
...
props
}
/>);
};
describe
(
'Render'
,
()
=>
{
it
(
'should render component'
,
()
=>
{
const
{
wrapper
}
=
setup
();
const
wrapper
=
setup
();
expect
(
wrapper
).
toMatchSnapshot
();
});
it
(
'should render list'
,
()
=>
{
const
wrapper
=
setup
({
hasFetched
:
true
,
});
expect
(
wrapper
).
toMatchSnapshot
();
});
...
...
public/app/features/plugins/PluginListPage.tsx
View file @
02e7d713
...
...
@@ -3,6 +3,7 @@ import { hot } from 'react-hot-loader';
import
{
connect
}
from
'react-redux'
;
import
PageHeader
from
'app/core/components/PageHeader/PageHeader'
;
import
OrgActionBar
from
'app/core/components/OrgActionBar/OrgActionBar'
;
import
PageLoader
from
'app/core/components/PageLoader/PageLoader'
;
import
PluginList
from
'./PluginList'
;
import
{
NavModel
,
Plugin
}
from
'app/types'
;
import
{
loadPlugins
,
setPluginsLayoutMode
,
setPluginsSearchQuery
}
from
'./state/actions'
;
...
...
@@ -15,6 +16,7 @@ export interface Props {
plugins
:
Plugin
[];
layoutMode
:
LayoutMode
;
searchQuery
:
string
;
hasFetched
:
boolean
;
loadPlugins
:
typeof
loadPlugins
;
setPluginsLayoutMode
:
typeof
setPluginsLayoutMode
;
setPluginsSearchQuery
:
typeof
setPluginsSearchQuery
;
...
...
@@ -30,12 +32,21 @@ export class PluginListPage extends PureComponent<Props> {
}
render
()
{
const
{
navModel
,
plugins
,
layoutMode
,
setPluginsLayoutMode
,
setPluginsSearchQuery
,
searchQuery
}
=
this
.
props
;
const
{
hasFetched
,
navModel
,
plugins
,
layoutMode
,
setPluginsLayoutMode
,
setPluginsSearchQuery
,
searchQuery
,
}
=
this
.
props
;
const
linkButton
=
{
href
:
'https://grafana.com/plugins?utm_source=grafana_plugin_list'
,
title
:
'Find more plugins on Grafana.com'
,
};
return
(
<
div
>
<
PageHeader
model=
{
navModel
}
/>
...
...
@@ -47,7 +58,11 @@ export class PluginListPage extends PureComponent<Props> {
setSearchQuery=
{
query
=>
setPluginsSearchQuery
(
query
)
}
linkButton=
{
linkButton
}
/>
{
plugins
&&
<
PluginList
plugins=
{
plugins
}
layoutMode=
{
layoutMode
}
/>
}
{
hasFetched
?
(
plugins
&&
<
PluginList
plugins=
{
plugins
}
layoutMode=
{
layoutMode
}
/>
)
:
(
<
PageLoader
pageName=
"Plugins"
/>
)
}
</
div
>
</
div
>
);
...
...
@@ -60,6 +75,7 @@ function mapStateToProps(state) {
plugins
:
getPlugins
(
state
.
plugins
),
layoutMode
:
getLayoutMode
(
state
.
plugins
),
searchQuery
:
getPluginsSearchQuery
(
state
.
plugins
),
hasFetched
:
state
.
plugins
.
hasFetched
,
};
}
...
...
public/app/features/plugins/__snapshots__/PluginListPage.test.tsx.snap
View file @
02e7d713
...
...
@@ -20,6 +20,33 @@ exports[`Render should render component 1`] = `
searchQuery=""
setSearchQuery={[Function]}
/>
<PageLoader
pageName="Plugins"
/>
</div>
</div>
`;
exports[`Render should render list 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
>
<OrgActionBar
layoutMode="grid"
linkButton={
Object {
"href": "https://grafana.com/plugins?utm_source=grafana_plugin_list",
"title": "Find more plugins on Grafana.com",
}
}
onSetLayoutMode={[Function]}
searchQuery=""
setSearchQuery={[Function]}
/>
<PluginList
layoutMode="grid"
plugins={Array []}
...
...
public/app/features/plugins/state/reducers.ts
View file @
02e7d713
...
...
@@ -6,12 +6,13 @@ export const initialState: PluginsState = {
plugins
:
[]
as
Plugin
[],
searchQuery
:
''
,
layoutMode
:
LayoutModes
.
Grid
,
hasFetched
:
false
,
};
export
const
pluginsReducer
=
(
state
=
initialState
,
action
:
Action
):
PluginsState
=>
{
switch
(
action
.
type
)
{
case
ActionTypes
.
LoadPlugins
:
return
{
...
state
,
plugins
:
action
.
payload
};
return
{
...
state
,
hasFetched
:
true
,
plugins
:
action
.
payload
};
case
ActionTypes
.
SetPluginsSearchQuery
:
return
{
...
state
,
searchQuery
:
action
.
payload
};
...
...
public/app/features/teams/TeamList.test.tsx
View file @
02e7d713
...
...
@@ -13,6 +13,7 @@ const setup = (propOverrides?: object) => {
setSearchQuery
:
jest
.
fn
(),
searchQuery
:
''
,
teamsCount
:
0
,
hasFetched
:
false
,
};
Object
.
assign
(
props
,
propOverrides
);
...
...
@@ -36,6 +37,7 @@ describe('Render', () => {
const
{
wrapper
}
=
setup
({
teams
:
getMultipleMockTeams
(
5
),
teamsCount
:
5
,
hasFetched
:
true
,
});
expect
(
wrapper
).
toMatchSnapshot
();
...
...
public/app/features/teams/TeamList.tsx
View file @
02e7d713
...
...
@@ -4,6 +4,7 @@ import { hot } from 'react-hot-loader';
import
PageHeader
from
'app/core/components/PageHeader/PageHeader'
;
import
DeleteButton
from
'app/core/components/DeleteButton/DeleteButton'
;
import
EmptyListCTA
from
'app/core/components/EmptyListCTA/EmptyListCTA'
;
import
PageLoader
from
'app/core/components/PageLoader/PageLoader'
;
import
{
NavModel
,
Team
}
from
'../../types'
;
import
{
loadTeams
,
deleteTeam
,
setSearchQuery
}
from
'./state/actions'
;
import
{
getSearchQuery
,
getTeams
,
getTeamsCount
}
from
'./state/selectors'
;
...
...
@@ -14,6 +15,7 @@ export interface Props {
teams
:
Team
[];
searchQuery
:
string
;
teamsCount
:
number
;
hasFetched
:
boolean
;
loadTeams
:
typeof
loadTeams
;
deleteTeam
:
typeof
deleteTeam
;
setSearchQuery
:
typeof
setSearchQuery
;
...
...
@@ -125,13 +127,23 @@ export class TeamList extends PureComponent<Props, any> {
);
}
renderList
()
{
const
{
teamsCount
}
=
this
.
props
;
if
(
teamsCount
>
0
)
{
return
this
.
renderTeamList
();
}
else
{
return
this
.
renderEmptyList
();
}
}
render
()
{
const
{
navModel
,
teamsCount
}
=
this
.
props
;
const
{
hasFetched
,
navModel
}
=
this
.
props
;
return
(
<
div
>
<
PageHeader
model=
{
navModel
}
/>
{
teamsCount
>
0
?
this
.
renderTeamList
()
:
this
.
renderEmptyList
()
}
{
hasFetched
?
this
.
renderList
()
:
<
PageLoader
pageName=
"Teams"
/>
}
</
div
>
);
}
...
...
@@ -143,6 +155,7 @@ function mapStateToProps(state) {
teams
:
getTeams
(
state
.
teams
),
searchQuery
:
getSearchQuery
(
state
.
teams
),
teamsCount
:
getTeamsCount
(
state
.
teams
),
hasFetched
:
state
.
teams
.
hasFetched
,
};
}
...
...
public/app/features/teams/__snapshots__/TeamList.test.tsx.snap
View file @
02e7d713
...
...
@@ -5,24 +5,9 @@ exports[`Render should render component 1`] = `
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
>
<EmptyListCTA
model={
Object {
"buttonIcon": "fa fa-plus",
"buttonLink": "org/teams/new",
"buttonTitle": " New team",
"proTip": "Assign folder and dashboard permissions to teams instead of users to ease administration.",
"proTipLink": "",
"proTipLinkTitle": "",
"proTipTarget": "_blank",
"title": "You haven't created any teams yet.",
}
}
<PageLoader
pageName="Teams"
/>
</div>
</div>
`;
...
...
public/app/features/teams/state/reducers.ts
View file @
02e7d713
import
{
Team
,
TeamGroup
,
TeamMember
,
TeamsState
,
TeamState
}
from
'app/types'
;
import
{
Action
,
ActionTypes
}
from
'./actions'
;
export
const
initialTeamsState
:
TeamsState
=
{
teams
:
[],
searchQuery
:
''
};
export
const
initialTeamsState
:
TeamsState
=
{
teams
:
[],
searchQuery
:
''
,
hasFetched
:
false
};
export
const
initialTeamState
:
TeamState
=
{
team
:
{}
as
Team
,
members
:
[]
as
TeamMember
[],
...
...
@@ -12,7 +12,7 @@ export const initialTeamState: TeamState = {
export
const
teamsReducer
=
(
state
=
initialTeamsState
,
action
:
Action
):
TeamsState
=>
{
switch
(
action
.
type
)
{
case
ActionTypes
.
LoadTeams
:
return
{
...
state
,
teams
:
action
.
payload
};
return
{
...
state
,
hasFetched
:
true
,
teams
:
action
.
payload
};
case
ActionTypes
.
SetSearchQuery
:
return
{
...
state
,
searchQuery
:
action
.
payload
};
...
...
public/app/features/teams/state/selectors.test.ts
View file @
02e7d713
...
...
@@ -7,7 +7,7 @@ describe('Teams selectors', () => {
const
mockTeams
=
getMultipleMockTeams
(
5
);
it
(
'should return teams if no search query'
,
()
=>
{
const
mockState
:
TeamsState
=
{
teams
:
mockTeams
,
searchQuery
:
''
};
const
mockState
:
TeamsState
=
{
teams
:
mockTeams
,
searchQuery
:
''
,
hasFetched
:
false
};
const
teams
=
getTeams
(
mockState
);
...
...
@@ -15,7 +15,7 @@ describe('Teams selectors', () => {
});
it
(
'Should filter teams if search query'
,
()
=>
{
const
mockState
:
TeamsState
=
{
teams
:
mockTeams
,
searchQuery
:
'5'
};
const
mockState
:
TeamsState
=
{
teams
:
mockTeams
,
searchQuery
:
'5'
,
hasFetched
:
false
};
const
teams
=
getTeams
(
mockState
);
...
...
public/app/features/users/UsersListPage.test.tsx
View file @
02e7d713
...
...
@@ -22,6 +22,7 @@ const setup = (propOverrides?: object) => {
updateUser
:
jest
.
fn
(),
removeUser
:
jest
.
fn
(),
setUsersSearchQuery
:
jest
.
fn
(),
hasFetched
:
false
,
};
Object
.
assign
(
props
,
propOverrides
);
...
...
@@ -41,6 +42,14 @@ describe('Render', () => {
expect
(
wrapper
).
toMatchSnapshot
();
});
it
(
'should render List page'
,
()
=>
{
const
{
wrapper
}
=
setup
({
hasFetched
:
true
,
});
expect
(
wrapper
).
toMatchSnapshot
();
});
});
describe
(
'Functions'
,
()
=>
{
...
...
public/app/features/users/UsersListPage.tsx
View file @
02e7d713
...
...
@@ -3,8 +3,9 @@ import { hot } from 'react-hot-loader';
import
{
connect
}
from
'react-redux'
;
import
Remarkable
from
'remarkable'
;
import
PageHeader
from
'app/core/components/PageHeader/PageHeader'
;
import
PageLoader
from
'app/core/components/PageLoader/PageLoader'
;
import
UsersActionBar
from
'./UsersActionBar'
;
import
UsersTable
from
'
app/features/users
/UsersTable'
;
import
UsersTable
from
'
.
/UsersTable'
;
import
InviteesTable
from
'./InviteesTable'
;
import
{
Invitee
,
NavModel
,
OrgUser
}
from
'app/types'
;
import
appEvents
from
'app/core/app_events'
;
...
...
@@ -18,6 +19,7 @@ export interface Props {
users
:
OrgUser
[];
searchQuery
:
string
;
externalUserMngInfo
:
string
;
hasFetched
:
boolean
;
loadUsers
:
typeof
loadUsers
;
loadInvitees
:
typeof
loadInvitees
;
setUsersSearchQuery
:
typeof
setUsersSearchQuery
;
...
...
@@ -87,8 +89,24 @@ export class UsersListPage extends PureComponent<Props, State> {
}));
};
renderTable
()
{
const
{
invitees
,
users
}
=
this
.
props
;
if
(
this
.
state
.
showInvites
)
{
return
<
InviteesTable
invitees=
{
invitees
}
onRevokeInvite=
{
code
=>
this
.
onRevokeInvite
(
code
)
}
/>;
}
else
{
return
(
<
UsersTable
users=
{
users
}
onRoleChange=
{
(
role
,
user
)
=>
this
.
onRoleChange
(
role
,
user
)
}
onRemoveUser=
{
user
=>
this
.
onRemoveUser
(
user
)
}
/>
);
}
}
render
()
{
const
{
invitees
,
navModel
,
users
}
=
this
.
props
;
const
{
navModel
,
hasFetched
}
=
this
.
props
;
const
externalUserMngInfoHtml
=
this
.
externalUserMngInfoHtml
;
return
(
...
...
@@ -99,15 +117,7 @@ export class UsersListPage extends PureComponent<Props, State> {
{
externalUserMngInfoHtml
&&
(
<
div
className=
"grafana-info-box"
dangerouslySetInnerHTML=
{
{
__html
:
externalUserMngInfoHtml
}
}
/>
)
}
{
this
.
state
.
showInvites
?
(
<
InviteesTable
invitees=
{
invitees
}
onRevokeInvite=
{
code
=>
this
.
onRevokeInvite
(
code
)
}
/>
)
:
(
<
UsersTable
users=
{
users
}
onRoleChange=
{
(
role
,
user
)
=>
this
.
onRoleChange
(
role
,
user
)
}
onRemoveUser=
{
user
=>
this
.
onRemoveUser
(
user
)
}
/>
)
}
{
hasFetched
?
this
.
renderTable
()
:
<
PageLoader
pageName=
"Users"
/>
}
</
div
>
</
div
>
);
...
...
@@ -121,6 +131,7 @@ function mapStateToProps(state) {
searchQuery
:
getUsersSearchQuery
(
state
.
users
),
invitees
:
getInvitees
(
state
.
users
),
externalUserMngInfo
:
state
.
users
.
externalUserMngInfo
,
hasFetched
:
state
.
users
.
hasFetched
,
};
}
...
...
public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap
View file @
02e7d713
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render
component
1`] = `
exports[`Render should render
List page
1`] = `
<div>
<PageHeader
model={Object {}}
...
...
@@ -20,3 +20,22 @@ exports[`Render should render component 1`] = `
</div>
</div>
`;
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
>
<Connect(UsersActionBar)
onShowInvites={[Function]}
showInvites={false}
/>
<PageLoader
pageName="Users"
/>
</div>
</div>
`;
public/app/features/users/state/reducers.ts
View file @
02e7d713
import
{
Invitee
,
OrgUser
,
UsersState
}
from
'app/types'
;
import
{
Action
,
ActionTypes
}
from
'./actions'
;
import
config
from
'
../../..
/core/config'
;
import
config
from
'
app
/core/config'
;
export
const
initialState
:
UsersState
=
{
invitees
:
[]
as
Invitee
[],
...
...
@@ -10,15 +10,16 @@ export const initialState: UsersState = {
externalUserMngInfo
:
config
.
externalUserMngInfo
,
externalUserMngLinkName
:
config
.
externalUserMngLinkName
,
externalUserMngLinkUrl
:
config
.
externalUserMngLinkUrl
,
hasFetched
:
false
,
};
export
const
usersReducer
=
(
state
=
initialState
,
action
:
Action
):
UsersState
=>
{
switch
(
action
.
type
)
{
case
ActionTypes
.
LoadUsers
:
return
{
...
state
,
users
:
action
.
payload
};
return
{
...
state
,
hasFetched
:
true
,
users
:
action
.
payload
};
case
ActionTypes
.
LoadInvitees
:
return
{
...
state
,
invitees
:
action
.
payload
};
return
{
...
state
,
hasFetched
:
true
,
invitees
:
action
.
payload
};
case
ActionTypes
.
SetUsersSearchQuery
:
return
{
...
state
,
searchQuery
:
action
.
payload
};
...
...
public/app/types/apiKeys.ts
View file @
02e7d713
...
...
@@ -14,4 +14,5 @@ export interface NewApiKey {
export
interface
ApiKeysState
{
keys
:
ApiKey
[];
searchQuery
:
string
;
hasFetched
:
boolean
;
}
public/app/types/datasources.ts
View file @
02e7d713
...
...
@@ -25,4 +25,5 @@ export interface DataSourcesState {
layoutMode
:
LayoutMode
;
dataSourcesCount
:
number
;
dataSourceTypes
:
Plugin
[];
hasFetched
:
boolean
;
}
public/app/types/plugins.ts
View file @
02e7d713
...
...
@@ -44,4 +44,5 @@ export interface PluginsState {
plugins
:
Plugin
[];
searchQuery
:
string
;
layoutMode
:
string
;
hasFetched
:
boolean
;
}
public/app/types/teams.ts
View file @
02e7d713
...
...
@@ -23,6 +23,7 @@ export interface TeamGroup {
export
interface
TeamsState
{
teams
:
Team
[];
searchQuery
:
string
;
hasFetched
:
boolean
;
}
export
interface
TeamState
{
...
...
public/app/types/user.ts
View file @
02e7d713
...
...
@@ -41,4 +41,5 @@ export interface UsersState {
externalUserMngLinkUrl
:
string
;
externalUserMngLinkName
:
string
;
externalUserMngInfo
:
string
;
hasFetched
:
boolean
;
}
public/app/types/users.ts
deleted
100644 → 0
View file @
974eddee
export
interface
Invitee
{
code
:
string
;
createdOn
:
string
;
email
:
string
;
emailSent
:
boolean
;
emailSentOn
:
string
;
id
:
number
;
invitedByEmail
:
string
;
invitedByLogin
:
string
;
invitedByName
:
string
;
name
:
string
;
orgId
:
number
;
role
:
string
;
status
:
string
;
url
:
string
;
}
export
interface
User
{
avatarUrl
:
string
;
email
:
string
;
lastSeenAt
:
string
;
lastSeenAtAge
:
string
;
login
:
string
;
orgId
:
number
;
role
:
string
;
userId
:
number
;
}
export
interface
UsersState
{
users
:
User
[];
invitees
:
Invitee
[];
searchQuery
:
string
;
canInvite
:
boolean
;
externalUserMngLinkUrl
:
string
;
externalUserMngLinkName
:
string
;
externalUserMngInfo
:
string
;
}
public/sass/_grafana.scss
View file @
02e7d713
...
...
@@ -95,7 +95,8 @@
@import
'components/user-picker'
;
@import
'components/description-picker'
;
@import
'components/delete_button'
;
@import
'components/_add_data_source.scss'
;
@import
'components/add_data_source.scss'
;
@import
'components/page_loader'
;
// PAGES
@import
'pages/login'
;
...
...
public/sass/components/_page_loader.scss
0 → 100644
View file @
02e7d713
.page-loader-wrapper
{
padding-top
:
100px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-direction
:
column
;
&
__spinner
{
font-size
:
32px
;
margin-bottom
:
$panel-margin
;
}
&
__text
{
font-size
:
14px
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment