Merge "Add an info banner"
This commit is contained in:
commit
4cb7887d00
7 files changed
+184
-5
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Tenant admins may now use the tenant management event dialog in
|
||||
the web UI to add an "info" banner that appears at the top of the
|
||||
status page. This can be used to make users aware of operational
|
||||
issues.
|
||||
@@ -4205,6 +4205,129 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
||||
dict(name='project-test2', result='SUCCESS', changes='2,1'),
|
||||
], ordered=False)
|
||||
|
||||
def test_state_post_info(self):
|
||||
# Test setting an info banner and separately pausing events
|
||||
self.executor_server.hold_jobs_in_build = False
|
||||
|
||||
authz = {'iss': 'zuul_operator',
|
||||
'aud': 'zuul.example.com',
|
||||
'sub': 'testuser',
|
||||
'zuul': {
|
||||
'admin': ['tenant-one', ],
|
||||
},
|
||||
'exp': int(time.time()) + 3600,
|
||||
'iat': int(time.time())}
|
||||
token = jwt.encode(authz, key='ThisIsABadSecretOnlyUsedForTesting',
|
||||
algorithm='HS256')
|
||||
info_msg = 'This is a test message'
|
||||
args = {
|
||||
'info': info_msg,
|
||||
}
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/state',
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
json=args)
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
time.sleep(1)
|
||||
|
||||
resp = self.get_url("api/tenant/tenant-one/status",
|
||||
headers={'Authorization': 'Bearer %s' % token})
|
||||
data = resp.json()
|
||||
self.assertEqual(info_msg, data['state']['info'])
|
||||
self.assertEqual(None, data['state']['reason'])
|
||||
|
||||
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
|
||||
self.waitUntilSettled()
|
||||
self.assertHistory([
|
||||
dict(name='project-merge', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test1', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test2', result='SUCCESS', changes='1,1'),
|
||||
], ordered=False)
|
||||
|
||||
pause_msg = 'test trigger paused'
|
||||
args = {
|
||||
'trigger_queue_paused': True,
|
||||
'reason': pause_msg,
|
||||
'info': info_msg,
|
||||
}
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/state',
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
json=args)
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
time.sleep(1)
|
||||
|
||||
resp = self.get_url("api/tenant/tenant-one/status",
|
||||
headers={'Authorization': 'Bearer %s' % token})
|
||||
data = resp.json()
|
||||
self.assertEqual(info_msg, data['state']['info'])
|
||||
self.assertEqual(pause_msg, data['state']['reason'])
|
||||
|
||||
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
|
||||
self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
|
||||
time.sleep(5)
|
||||
|
||||
self.assertHistory([
|
||||
dict(name='project-merge', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test1', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test2', result='SUCCESS', changes='1,1'),
|
||||
], ordered=False)
|
||||
self.assertEqual(0, len(self.builds))
|
||||
|
||||
pause_msg = 'test result paused'
|
||||
args = {
|
||||
'trigger_queue_paused': True,
|
||||
'result_queue_paused': True,
|
||||
'reason': pause_msg,
|
||||
}
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/state',
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
json=args)
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
|
||||
time.sleep(5)
|
||||
resp = self.get_url("api/tenant/tenant-one/status",
|
||||
headers={'Authorization': 'Bearer %s' % token})
|
||||
data = resp.json()
|
||||
self.assertEqual(None, data['state']['info'])
|
||||
self.assertEqual(pause_msg, data['state']['reason'])
|
||||
|
||||
self.assertHistory([
|
||||
dict(name='project-merge', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test1', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test2', result='SUCCESS', changes='1,1'),
|
||||
], ordered=False)
|
||||
self.assertEqual(0, len(self.builds))
|
||||
|
||||
args = {
|
||||
'trigger_queue_paused': False,
|
||||
'result_queue_paused': False,
|
||||
'reason': None,
|
||||
}
|
||||
req = self.post_url(
|
||||
'api/tenant/tenant-one/state',
|
||||
headers={'Authorization': 'Bearer %s' % token},
|
||||
json=args)
|
||||
self.assertEqual(200, req.status_code, req.text)
|
||||
|
||||
self.waitUntilSettled()
|
||||
self.assertHistory([
|
||||
dict(name='project-merge', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test1', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-test2', result='SUCCESS', changes='1,1'),
|
||||
dict(name='project-merge', result='SUCCESS', changes='2,1'),
|
||||
dict(name='project-test1', result='SUCCESS', changes='2,1'),
|
||||
dict(name='project-test2', result='SUCCESS', changes='2,1'),
|
||||
], ordered=False)
|
||||
time.sleep(5)
|
||||
resp = self.get_url("api/tenant/tenant-one/status",
|
||||
headers={'Authorization': 'Bearer %s' % token})
|
||||
data = resp.json()
|
||||
self.assertEqual(None, data['state']['info'])
|
||||
self.assertEqual(None, data['state']['reason'])
|
||||
|
||||
def test_state_post_timer(self):
|
||||
self.executor_server.hold_jobs_in_build = False
|
||||
|
||||
|
||||
+2
-1
@@ -192,7 +192,7 @@ function fetchChangeStatus(apiPrefix, changeId) {
|
||||
return makeRequest(apiPrefix + 'status/change/' + changeId)
|
||||
}
|
||||
|
||||
function setTenantState(apiPrefix, discardTriggerEvents, pauseTriggerQueue, pauseResultQueue, reason) {
|
||||
function setTenantState(apiPrefix, discardTriggerEvents, pauseTriggerQueue, pauseResultQueue, reason, info) {
|
||||
return makeRequest(
|
||||
apiPrefix + 'state',
|
||||
'post',
|
||||
@@ -201,6 +201,7 @@ function setTenantState(apiPrefix, discardTriggerEvents, pauseTriggerQueue, paus
|
||||
trigger_queue_paused: pauseTriggerQueue,
|
||||
result_queue_paused: pauseResultQueue,
|
||||
reason: reason,
|
||||
info: info,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { setTenantState } from '../../api'
|
||||
function PauseModal({isOpen, setOpen}) {
|
||||
const tenant = useSelector((state) => state.tenant)
|
||||
const [reason, setReason] = useState('')
|
||||
const [info, setInfo] = useState('')
|
||||
const [queueState, setQueueState] = useState('normal')
|
||||
const [discardTrigger, setDiscardTrigger] = useState(false)
|
||||
const [pauseTrigger, setPauseTrigger] = useState(false)
|
||||
@@ -52,7 +53,8 @@ function PauseModal({isOpen, setOpen}) {
|
||||
discardTrigger,
|
||||
pauseTrigger,
|
||||
pauseResult,
|
||||
reason)
|
||||
reason,
|
||||
info)
|
||||
.catch(error => {
|
||||
dispatch(addApiError(error))
|
||||
})
|
||||
@@ -116,7 +118,7 @@ function PauseModal({isOpen, setOpen}) {
|
||||
<FormGroup
|
||||
label="Reason"
|
||||
fieldId="pause-form-reason"
|
||||
helperText="This explanation will appear on the status page.">
|
||||
helperText="This explanation will appear on the status page while event processing is inactive.">
|
||||
<TextInput
|
||||
value={reason}
|
||||
isRequired
|
||||
@@ -126,6 +128,18 @@ function PauseModal({isOpen, setOpen}) {
|
||||
onChange={(value) => { setReason(value) }}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label="Info"
|
||||
fieldId="pause-form-info"
|
||||
helperText="This text will appear in a separate info banner on the status page, regardless of event processing status.">
|
||||
<TextInput
|
||||
value={info}
|
||||
type="text"
|
||||
id="pause-form-info"
|
||||
name="pauseInfo"
|
||||
onChange={(value) => { setInfo(value) }}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
FlexItem,
|
||||
} from '@patternfly/react-core'
|
||||
import {
|
||||
InfoCircleIcon,
|
||||
StreamIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from '@patternfly/react-icons'
|
||||
@@ -127,11 +128,25 @@ PipelineGallery.propTypes = {
|
||||
}
|
||||
|
||||
|
||||
function renderBanner(status)
|
||||
function renderInfoBanner(status)
|
||||
{
|
||||
if (!status || !status.state) {
|
||||
if (!status || !status.state.info) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<Banner screenReaderText="Info banner" variant="info">
|
||||
<Flex spaceItems={{ default: 'spaceItemsSm' }}>
|
||||
<FlexItem>
|
||||
<InfoCircleIcon />
|
||||
</FlexItem>
|
||||
<FlexItem>{status.state.info}</FlexItem>
|
||||
</Flex>
|
||||
</Banner>
|
||||
)
|
||||
}
|
||||
|
||||
function renderPauseBanner(status)
|
||||
{
|
||||
// This method is able to describe states that the client libraries
|
||||
// do not support creating (for example, buth discarding and pausing
|
||||
// the trigger event queue).
|
||||
@@ -167,6 +182,19 @@ function renderBanner(status)
|
||||
)
|
||||
}
|
||||
|
||||
function renderBanner(status)
|
||||
{
|
||||
if (!status || !status.state) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{renderInfoBanner(status)}
|
||||
{renderPauseBanner(status)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function getPipelines(status, location, filterCategories) {
|
||||
let pipelines = []
|
||||
let stats = {}
|
||||
|
||||
@@ -12238,6 +12238,7 @@ class TenantEventState(zkobject.ZKObject):
|
||||
trigger_queue_paused=False,
|
||||
result_queue_paused=False,
|
||||
reason=None,
|
||||
info=None,
|
||||
)
|
||||
|
||||
def getPath(self):
|
||||
@@ -12249,6 +12250,7 @@ class TenantEventState(zkobject.ZKObject):
|
||||
"trigger_queue_paused": self.trigger_queue_paused,
|
||||
"result_queue_paused": self.result_queue_paused,
|
||||
"reason": self.reason,
|
||||
"info": self.info,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@@ -1698,6 +1698,9 @@ class ZuulWebAPI(object):
|
||||
# We limit the reason to 4096 chars to limit the
|
||||
# zk node size.
|
||||
reason = str(reason[:4096])
|
||||
info = body.get('info')
|
||||
if info:
|
||||
info = str(info[:4096])
|
||||
tqs._set(
|
||||
trigger_queue_discarding=bool(
|
||||
body.get('trigger_queue_discarding',
|
||||
@@ -1709,6 +1712,7 @@ class ZuulWebAPI(object):
|
||||
body.get('result_queue_paused',
|
||||
current_state.result_queue_paused)),
|
||||
reason=reason,
|
||||
info=info,
|
||||
)
|
||||
tqs.internalCreate(ctx)
|
||||
|
||||
|
||||
Reference in new issue
Block a user