diff --git a/web-ui/Dockerfile b/web-ui/Dockerfile index ac0aa0c1f..7e75eec1d 100644 --- a/web-ui/Dockerfile +++ b/web-ui/Dockerfile @@ -1,16 +1,30 @@ -FROM registry.opensource.zalan.do/library/node-18-alpine:latest +FROM registry.opensource.zalan.do/library/node-18-alpine:latest AS builder MAINTAINER "http://zalando.github.io/" -RUN apk --no-cache add curl - -COPY package.json ./ -COPY yarn.lock ./ +WORKDIR /app -RUN yarn --production +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile COPY src ./src COPY server.js ./ +COPY webpack ./webpack +COPY .babelrc ./ + +RUN yarn build + +FROM registry.opensource.zalan.do/library/node-18-alpine:latest AS runtime + +WORKDIR /app + +RUN apk --no-cache add curl + +COPY package.json yarn.lock ./ +RUN yarn install --production --frozen-lockfile + +COPY --from=builder /app/src ./src +COPY --from=builder /app/server.js ./ EXPOSE 3000 diff --git a/web-ui/src/client/app/components/__tests__/violations.test.jsx b/web-ui/src/client/app/components/__tests__/violations.test.jsx index fa69aaa53..9e27fc207 100644 --- a/web-ui/src/client/app/components/__tests__/violations.test.jsx +++ b/web-ui/src/client/app/components/__tests__/violations.test.jsx @@ -53,6 +53,20 @@ describe('Violations component', () => { const component = shallow(); expect(component.find('Violation')).toHaveLength(0); }); + + test('should show MUST and SHOULD counts', () => { + const violationsCount = { must: 2, should: 3 }; + const component = shallow( + + ); + const counts = component.find('.violations-heading__count'); + + expect(counts).toHaveLength(2); + expect(counts.at(0).text()).toContain('MUST:2'); + expect(counts.at(1).text()).toContain('SHOULD:3'); + expect(counts.at(0).find('RuleType')).toHaveLength(1); + expect(counts.at(1).find('RuleType')).toHaveLength(1); + }); }); describe('Violation component', () => { @@ -168,13 +182,14 @@ describe('ViolationsResult component', () => { describe('when state is complete with violations', () => { test('should render violations', () => { const violations = [{}, {}]; + const violationsCount = { must: 1, should: 2 }; const component = shallow( @@ -186,7 +201,7 @@ describe('ViolationsResult component', () => { expect(Violations).toHaveLength(1); expect(Violations.prop('violations')).toEqual(violations); - expect(Violations.prop('violationsCount')).toEqual(2); + expect(Violations.prop('violationsCount')).toEqual(violationsCount); }); }); }); diff --git a/web-ui/src/client/app/components/violations.jsx b/web-ui/src/client/app/components/violations.jsx index 794c51ccb..00e0b9665 100644 --- a/web-ui/src/client/app/components/violations.jsx +++ b/web-ui/src/client/app/components/violations.jsx @@ -6,14 +6,42 @@ import FluidContainer from './fluid-container.jsx'; import { Link } from 'react-router-dom'; export function Violations(props) { + const { + externalId, + violations = [], + violationsCount = {}, + } = props; + + const parseCount = value => + typeof value === 'number' ? value : 0; + + const severityCounts = [ + { label: 'MUST', value: parseCount(violationsCount.must) }, + { label: 'SHOULD', value: parseCount(violationsCount.should) }, + ]; + return (
-

- VIOLATIONS - - +

+ VIOLATIONS + + {severityCounts.map(({ label, value }) => ( + + {label}: + + {value} + + + + ))} + + + @@ -25,7 +53,7 @@ export function Violations(props) {
    - {props.violations.map((violation, index) => { + {violations.map((violation, index) => { return ; })}
diff --git a/web-ui/src/client/app/containers/__tests__/violations.test.jsx b/web-ui/src/client/app/containers/__tests__/violations.test.jsx index 4e4a15a8e..f32aa666e 100644 --- a/web-ui/src/client/app/containers/__tests__/violations.test.jsx +++ b/web-ui/src/client/app/containers/__tests__/violations.test.jsx @@ -27,7 +27,7 @@ describe('Violations container component', () => { describe('when call handleFormSubmit', () => { test('should handle success', () => { const violations = [{}]; - const violationsCount = 1; + const violationsCount = { must: 1, should: 0 }; getApiViolations.mockReturnValueOnce( Promise.resolve({ violations: violations, diff --git a/web-ui/src/client/app/containers/editor.jsx b/web-ui/src/client/app/containers/editor.jsx index 41e1e8393..a2a7ca3dd 100644 --- a/web-ui/src/client/app/containers/editor.jsx +++ b/web-ui/src/client/app/containers/editor.jsx @@ -135,28 +135,30 @@ export class Editor extends Violations { /> ) : null}
- +
- +

); diff --git a/web-ui/src/client/app/containers/url.jsx b/web-ui/src/client/app/containers/url.jsx index 4f8fea860..06bc194cf 100644 --- a/web-ui/src/client/app/containers/url.jsx +++ b/web-ui/src/client/app/containers/url.jsx @@ -62,6 +62,7 @@ export class URL extends Violations { errorMsgText={this.state.error} externalId={this.state.externalId} violations={this.state.violations} + violationsCount={this.state.violationsCount} successMsgTitle={this.state.successMsgTitle} successMsgText={this.state.successMsgText} /> diff --git a/web-ui/src/client/app/index.scss b/web-ui/src/client/app/index.scss index 2de4069a8..87e72a140 100644 --- a/web-ui/src/client/app/index.scss +++ b/web-ui/src/client/app/index.scss @@ -200,6 +200,50 @@ footer { height: 90%; } +.violations-heading { + margin: 0 0 1rem; + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.75rem; +} + +.violations-heading__counts { + display: flex; + align-items: baseline; + gap: 0.5rem; + font-size: 1rem; + color: #1f1f1f; +} + +.violations-heading__count { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: inherit; + color: inherit; +} + +.violations-heading__count strong { + font-size: inherit; + font-weight: 600; + color: inherit; +} + +.violations-heading__count-value { + font-weight: 500; +} + +.violations-heading__count + .violations-heading__count::before { + content: '/'; + margin: 0 0.45rem; + color: #d1d1d1; +} + +.violations-heading__link { + margin-left: auto; +} + .dc-column { height: 100%; } @@ -209,4 +253,3 @@ footer { height: 80%; } } -