diff --git a/frontend/.eslintrc b/frontend/.eslintrc
index 36921ca49..2f5e7b7af 100644
--- a/frontend/.eslintrc
+++ b/frontend/.eslintrc
@@ -15,7 +15,9 @@
"EXPORTER_GROUP": true,
"OC_GROUP": true,
"EXPORTER_MODE": true,
- "ZONE": true,
+ "CODE_EXPORT_ENABLED": true,
+ "REPOSITORY_HOST": true,
+ "ZONE": true
"HELP_URL": true
},
"parser": "babel-eslint",
diff --git a/frontend/config/default.json.example b/frontend/config/default.json.example
index a27a770a4..9c5625e28 100644
--- a/frontend/config/default.json.example
+++ b/frontend/config/default.json.example
@@ -13,6 +13,7 @@
"ocGroup": "/oc",
"exporterMode": "download",
"codeExportEnabled": false,
+ "repositoryHost": "any code repository regex",
"auth": {
"authorizationEndpoint": "URL_REQUIRED",
"callbackURL": "http://localhost:8000/auth",
diff --git a/frontend/config/test.json.example b/frontend/config/test.json.example
index 27b3675d9..670edbac9 100644
--- a/frontend/config/test.json.example
+++ b/frontend/config/test.json.example
@@ -11,8 +11,9 @@
"filesApiHost": "localhost:1080",
"exporterGroup": "/exporter",
"ocGroup": "/oc",
- "exporterMode": "export",
- "codeExportEnabled": true,
+ "exporterMode": "download",
+ "codeExportEnabled": false,
+ "repositoryHost": "any code repository regex",
"auth": {
"authorizationEndpoint": "URL_REQUIRED",
"callbackURL": "http://localhost:8000/auth",
diff --git a/frontend/entrypoint.sh b/frontend/entrypoint.sh
index 733652b74..80d84074b 100644
--- a/frontend/entrypoint.sh
+++ b/frontend/entrypoint.sh
@@ -13,6 +13,7 @@ printf "\"exporterGroup\": \"${EXPORTER_GROUP}\",\n" >> ./config/default.json
printf "\"ocGroup\": \"${OC_GROUP}\",\n" >> ./config/default.json
printf "\"exporterMode\": \"${EXPORTER_MODE}\",\n" >> ./config/default.json
printf "\"codeExportEnabled\": \"${CODE_EXPORT_ENABLED}\",\n" >> ./config/default.json
+printf "\"repositoryHost\": \"${REPOSITORY_HOST}\",\n" >> ./config/default.json
printf "\"cookieSecret\": \"${COOKIE_SECRET}\",\n" >> ./config/default.json
printf "\"jwtSecret\": \"${JWT_SECRET}\",\n" >> ./config/default.json
printf "\"auth\": {\n" >> ./config/default.json
diff --git a/frontend/helm/ocwa-frontend/templates/deployment.yaml b/frontend/helm/ocwa-frontend/templates/deployment.yaml
index 5d4a1fbfe..3331e6c03 100644
--- a/frontend/helm/ocwa-frontend/templates/deployment.yaml
+++ b/frontend/helm/ocwa-frontend/templates/deployment.yaml
@@ -46,6 +46,8 @@ spec:
value: "export"
- name: CODE_EXPORT_ENABLED
value: "{{ .Values.codeExportEnabled }}"
+ - name: REPOSITORY_HOST
+ value: "{{ .Values.repositoryHost }}"
- name: COOKIE_SECRET
valueFrom:
secretKeyRef:
diff --git a/frontend/helm/ocwa-frontend/templates/downloadDeployment.yaml b/frontend/helm/ocwa-frontend/templates/downloadDeployment.yaml
index 1f6bb4354..058e3982a 100644
--- a/frontend/helm/ocwa-frontend/templates/downloadDeployment.yaml
+++ b/frontend/helm/ocwa-frontend/templates/downloadDeployment.yaml
@@ -54,6 +54,8 @@ spec:
value: "download"
- name: CODE_EXPORT_ENABLED
value: "{{ .Values.codeExportEnabled }}"
+ - name: REPOSITORY_HOST
+ value: "{{ .Values.repositoryHost }}"
- name: COOKIE_SECRET
valueFrom:
secretKeyRef:
diff --git a/frontend/helm/ocwa-frontend/values.yaml b/frontend/helm/ocwa-frontend/values.yaml
index d8fefc2ef..51bded53b 100644
--- a/frontend/helm/ocwa-frontend/values.yaml
+++ b/frontend/helm/ocwa-frontend/values.yaml
@@ -51,6 +51,7 @@ filesApiHost: "ocwa-storage-api-mongo.ocwa"
exporterGroup: "/exporter"
ocGroup: "/oc"
codeExportEnabled: false
+repositoryHost: "repository-url"
auth:
authorizationEndpoint: "openid.auth.endpoint"
callbackURL: "chart-example.local/auth"
diff --git a/frontend/server/app.js b/frontend/server/app.js
index a7b2379ed..6cfe5ccd4 100644
--- a/frontend/server/app.js
+++ b/frontend/server/app.js
@@ -33,6 +33,7 @@ const exporterGroup = config.get('exporterGroup');
const ocGroup = config.get('ocGroup');
const exporterMode = config.get('exporterMode');
const codeExportEnabled = config.get('codeExportEnabled');
+const repositoryHost = config.get('repositoryHost');
const memoryStore = new MemoryStore({
checkPeriod: 86400000, // prune expired entries every 24h
@@ -106,6 +107,7 @@ app.get('*', checkAuth, storeUrl, (req, res) => {
exporterGroup,
ocGroup,
exporterMode,
+ repositoryHost,
zone: getZone(),
});
});
diff --git a/frontend/server/views/index.pug b/frontend/server/views/index.pug
index a9f366797..1a5b865e5 100644
--- a/frontend/server/views/index.pug
+++ b/frontend/server/views/index.pug
@@ -19,6 +19,7 @@ html
window.OC_GROUP = !{JSON.stringify(ocGroup)};
window.EXPORTER_MODE = !{JSON.stringify(exporterMode)};
window.CODE_EXPORT_ENABLED = !{JSON.stringify(codeExportEnabled)}
+ window.REPOSITORY_HOST = !{JSON.stringify(repositoryHost)}
window.ZONE = !{JSON.stringify(zone)};
window.HELP_URL = !{JSON.stringify(helpURL)};
diff --git a/frontend/src/components/export-type-icon/index.jsx b/frontend/src/components/export-type-icon/index.jsx
index df08dc3e2..d5d3f74a2 100644
--- a/frontend/src/components/export-type-icon/index.jsx
+++ b/frontend/src/components/export-type-icon/index.jsx
@@ -14,11 +14,12 @@ function ExportTypeIcon({ exportType, large }) {
}
ExportTypeIcon.propTypes = {
- exportType: PropTypes.string.isRequired,
+ exportType: PropTypes.string,
large: PropTypes.bool,
};
ExportTypeIcon.defaultProps = {
+ exportType: '',
large: false,
};
diff --git a/frontend/src/modules/requests/__tests__/utils.test.js b/frontend/src/modules/requests/__tests__/utils.test.js
new file mode 100644
index 000000000..0052827a4
--- /dev/null
+++ b/frontend/src/modules/requests/__tests__/utils.test.js
@@ -0,0 +1,122 @@
+import { colors } from '@atlaskit/theme';
+
+import * as utils from '../utils';
+
+describe('request/utils', () => {
+ describe('phone number validation', () => {
+ const regex = new RegExp(utils.phoneNumberRegex);
+ it('should pass an xxx-xxx-xxxx phone number', () => {
+ expect(regex.test('555-555-5555')).toBeTruthy();
+ });
+ it('should pass a number only phone number', () => {
+ expect(regex.test('5555555555')).toBeTruthy();
+ });
+ it('should fail invalid phone numbers', () => {
+ expect(regex.test('abc-def-ghij')).toBeFalsy();
+ });
+ });
+
+ describe('url validation', () => {
+ it('should validate a git URL', () => {
+ const regex = new RegExp(utils.gitUrlRegex);
+ expect(regex.test('https://github.com/org/repo.git')).toBeTruthy();
+ expect(regex.test('asdfasdf')).toBeFalsy();
+ });
+
+ it('should validate a configurable repository URL', () => {
+ const regex = new RegExp(utils.repositoryRegex);
+ expect(
+ regex.test('https://example.com/shares/test/repo.git')
+ ).toBeTruthy();
+ expect(
+ regex.test('https://my-internal-website.com/test.git')
+ ).toBeFalsy();
+ });
+ });
+
+ describe('request colors', () => {
+ it('should default to N200 if it is not a state number value', () => {
+ expect(utils.getRequestStateColor()).toEqual(colors.N200);
+ expect(utils.getRequestStateColor(null)).toEqual(colors.N200);
+ expect(utils.getRequestStateColor(undefined)).toEqual(colors.N200);
+ expect(utils.getRequestStateColor('test')).toEqual(colors.N200);
+ });
+
+ it('should pass number states', () => {
+ expect(utils.getRequestStateColor(0)).toEqual(colors.N200);
+ expect(utils.getRequestStateColor(1)).toEqual(colors.N200);
+ expect(utils.getRequestStateColor(2)).toEqual(colors.Y300);
+ expect(utils.getRequestStateColor(3)).toEqual(colors.Y500);
+ expect(utils.getRequestStateColor(4)).toEqual(colors.G500);
+ expect(utils.getRequestStateColor(5)).toEqual(colors.R500);
+ expect(utils.getRequestStateColor(6)).toEqual(colors.R500);
+ });
+ });
+
+ describe('request duplication', () => {
+ it('should duplicate a data request payload', () => {
+ const original = {
+ _id: 1,
+ name: 'Duplicate Me',
+ phoneNumber: '5555555555',
+ exportType: 'data',
+ variableDescriptions: 'Description',
+ files: ['file1', 'file2'],
+ supportingFiles: ['sFile1', 'sFile2'],
+ };
+ expect(utils.duplicateRequest(original)).toEqual({
+ name: 'Duplicate Me Duplicate',
+ phoneNumber: '5555555555',
+ exportType: 'data',
+ variableDescriptions: 'Description',
+ files: ['file1', 'file2'],
+ supportingFiles: ['sFile1', 'sFile2'],
+ });
+ });
+
+ it('should duplicate a code request payload', () => {
+ const original = {
+ _id: 1,
+ name: 'Duplicate Me',
+ phoneNumber: '5555555555',
+ exportType: 'code',
+ repository: 'http://test.com',
+ branch: 'develop',
+ externalRepository: 'http://test.com',
+ codeDescription: 'Code Description',
+ };
+ expect(utils.duplicateRequest(original)).toEqual({
+ name: 'Duplicate Me Duplicate',
+ phoneNumber: '5555555555',
+ exportType: 'code',
+ repository: 'http://test.com',
+ branch: 'develop',
+ externalRepository: 'http://test.com',
+ codeDescription: 'Code Description',
+ });
+ });
+
+ it('should drop weird or nil values', () => {
+ const original = {
+ _id: 1,
+ name: 'Duplicate Me',
+ phoneNumber: null,
+ exportType: 'code',
+ repository: 'http://test.com',
+ branch: 'develop',
+ externalRepository: 'http://test.com',
+ codeDescription: 'Code Description',
+ outdatedField: 'asdfasdf',
+ };
+ expect(utils.duplicateRequest(original)).toEqual({
+ name: 'Duplicate Me Duplicate',
+ phoneNumber: '',
+ exportType: 'code',
+ repository: 'http://test.com',
+ branch: 'develop',
+ externalRepository: 'http://test.com',
+ codeDescription: 'Code Description',
+ });
+ });
+ });
+});
diff --git a/frontend/src/modules/requests/components/request-form/field.jsx b/frontend/src/modules/requests/components/request-form/field.jsx
index 6fafcc1c5..936bbcdd9 100644
--- a/frontend/src/modules/requests/components/request-form/field.jsx
+++ b/frontend/src/modules/requests/components/request-form/field.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import TextField from '@atlaskit/textfield';
import TextArea from '@atlaskit/textarea';
-import { phoneNumberRegex, urlRegex } from '../../utils';
+import { phoneNumberRegex, repositoryRegex, gitUrlRegex } from '../../utils';
function Field({ type, fieldProps }) {
switch (type) {
@@ -16,8 +16,13 @@ function Field({ type, fieldProps }) {
);
- case 'url':
- return ;
+ case 'repositoryHost':
+ return (
+
+ );
+
+ case 'git':
+ return ;
case 'text':
default:
@@ -26,7 +31,14 @@ function Field({ type, fieldProps }) {
}
Field.propTypes = {
- type: PropTypes.oneOf(['text', 'tel', 'textarea', 'url']).isRequired,
+ type: PropTypes.oneOf([
+ 'text',
+ 'tel',
+ 'textarea',
+ 'url',
+ 'git',
+ 'repositoryHost',
+ ]).isRequired,
fieldProps: PropTypes.object.isRequired,
};
diff --git a/frontend/src/modules/requests/components/request/edit-field.jsx b/frontend/src/modules/requests/components/request/edit-field.jsx
index f0d05ae30..311264d32 100644
--- a/frontend/src/modules/requests/components/request/edit-field.jsx
+++ b/frontend/src/modules/requests/components/request/edit-field.jsx
@@ -102,8 +102,14 @@ EditField.propTypes = {
data: PropTypes.shape({
key: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- type: PropTypes.oneOf(['text', 'textarea', 'tel', 'email', 'url'])
- .isRequired,
+ type: PropTypes.oneOf([
+ 'text',
+ 'tel',
+ 'textarea',
+ 'url',
+ 'git',
+ 'repositoryHost',
+ ]).isRequired,
isRequired: PropTypes.bool,
value: PropTypes.string.isRequired,
}).isRequired,
diff --git a/frontend/src/modules/requests/utils.js b/frontend/src/modules/requests/utils.js
index 3260a7488..e9d50533f 100644
--- a/frontend/src/modules/requests/utils.js
+++ b/frontend/src/modules/requests/utils.js
@@ -1,8 +1,10 @@
import { colors } from '@atlaskit/theme';
import flow from 'lodash/flow';
import isNil from 'lodash/isNil';
+import isNumber from 'lodash/isNumber';
import pick from 'lodash/pick';
import mapValues from 'lodash/mapValues';
+import { repositoryHost } from '@src/services/config';
import { _e, getZoneString } from '@src/utils';
// Form content
@@ -79,7 +81,7 @@ export const requestFields = [
external: 'Internal repository to send approved results',
}),
value: 'repository',
- type: 'url',
+ type: 'repositoryHost',
exportType: 'code',
isRequired: true,
helperText: 'Write out the full URL of the repository',
@@ -99,7 +101,7 @@ export const requestFields = [
external: 'External repository to send approved results',
}),
value: 'externalRepository',
- type: 'url',
+ type: 'git',
exportType: 'code',
isRequired: true,
helperText: 'Write out the full URL of the external repository',
@@ -108,11 +110,16 @@ export const requestFields = [
// Stored as a string here for native input[type="tel"] elements, so make into
// a RegExp if using anywhere else
-export const phoneNumberRegex = '[0-9]{3}-[0-9]{3}-[0-9]{4}$';
-export const urlRegex =
- '^(?:(?:https?|ftp)://)?(?:(?!(?:10|127)(?:.d{1,3}){3})(?!(?:169.254|192.168)(?:.d{1,3}){2})(?!172.(?:1[6-9]|2d|3[0-1])(?:.d{1,3}){2})(?:[1-9]d?|1dd|2[01]d|22[0-3])(?:.(?:1?d{1,2}|2[0-4]d|25[0-5])){2}(?:.(?:[1-9]d?|1dd|2[0-4]d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:.(?:[a-z\u00a1-\uffff]{2,})))(?::d{2,5})?(?:/S*)?$';
+export const phoneNumberRegex = '[0-9]{3}-?[0-9]{3}-?[0-9]{4}$';
+export const gitUrlRegex =
+ '((git|ssh|http(s)?)|(git@[w.]+))(:(//)?)([w.@:/-~]+)(.git)(/)?';
+export const repositoryRegex = repositoryHost
+ ? `${repositoryHost}([w.@:/-~]+)(.git)(/)?`
+ : gitUrlRegex;
export const getRequestStateColor = (value = 0) => {
+ if (!isNumber(value)) return colors.N200;
+
switch (value) {
case 0:
case 1:
@@ -125,9 +132,8 @@ export const getRequestStateColor = (value = 0) => {
return colors.G500;
case 5:
case 6:
- return colors.R500;
default:
- return null;
+ return colors.R500;
}
};
@@ -163,4 +169,6 @@ export default {
getRequestStateColor,
requestFields,
phoneNumberRegex,
+ repositoryRegex,
+ gitUrlRegex,
};
diff --git a/frontend/src/services/config.js b/frontend/src/services/config.js
index 228f61818..dbe0350a9 100644
--- a/frontend/src/services/config.js
+++ b/frontend/src/services/config.js
@@ -8,6 +8,7 @@ export const exporterGroup = EXPORTER_GROUP;
export const ocGroup = OC_GROUP;
export const exporterMode = EXPORTER_MODE; // Can be (undefined || 'export') or 'download'
export const codeExportEnabled = CODE_EXPORT_ENABLED; // Can be (undefined || 'export') or 'download'
+export const repositoryHost = REPOSITORY_HOST; // Can be (undefined || 'export') or 'download'
export const zone = ZONE;
export const helpURL = HELP_URL;
export const getZone = () => ZONE;
@@ -23,6 +24,7 @@ export default {
ocGroup,
exporterMode,
socketHost,
+ repositoryHost,
zone,
getZone,
};
diff --git a/helm/ocwa/values.yaml b/helm/ocwa/values.yaml
index cffd92d48..14d445b24 100644
--- a/helm/ocwa/values.yaml
+++ b/helm/ocwa/values.yaml
@@ -51,6 +51,7 @@ ocwa-frontend:
exporterGroup: "/exporter"
ocGroup: "/oc"
codeExportEnabled: false
+ repositoryHost: "repository-host"
auth:
authorizationEndpoint: "openid.auth.endpoint"
callbackURL: "chart-example.local/auth"