From e8b66061a90de45dc1850ec12119a9248b0c9f5f Mon Sep 17 00:00:00 2001 From: Luca Marchesini Date: Thu, 4 Feb 2021 16:13:56 +0100 Subject: [PATCH 01/16] [navbar] Added github icon (#900) --- src/components/Common/MainMenu.vue | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/Common/MainMenu.vue b/src/components/Common/MainMenu.vue index 364b3f3e4..d3e8b3dc0 100644 --- a/src/components/Common/MainMenu.vue +++ b/src/components/Common/MainMenu.vue @@ -35,6 +35,16 @@ > Security + + + Date: Thu, 4 Feb 2021 16:21:43 +0100 Subject: [PATCH 02/16] Fix Json-editor multiple instance (#894) --- src/components/Common/JsonEditor.vue | 38 +++++++++------ .../Documents/FormInputs/JsonFormInput.vue | 1 + .../single-backend/formView.spec.js | 46 +++++++++++++++---- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/components/Common/JsonEditor.vue b/src/components/Common/JsonEditor.vue index 758ad038f..7a68e7335 100644 --- a/src/components/Common/JsonEditor.vue +++ b/src/components/Common/JsonEditor.vue @@ -1,7 +1,8 @@ @@ -50,13 +46,16 @@ export default { components: { EnvironmentSwitch }, - data() { - return { - host: null, - port: null - } - }, computed: { + currentEnvironment() { + return this.$store.direct.getters.kuzzle.currentEnvironment + }, + host() { + return this.currentEnvironment ? this.currentEnvironment.host : '' + }, + port() { + return this.currentEnvironment ? this.currentEnvironment.port : '' + }, errorInternalMessage() { return this.$store.state.kuzzle.errorFromKuzzle } diff --git a/src/components/Data/Collections/CreateOrUpdate.vue b/src/components/Data/Collections/CreateOrUpdate.vue index 9a159802c..ed72e2134 100644 --- a/src/components/Data/Collections/CreateOrUpdate.vue +++ b/src/components/Data/Collections/CreateOrUpdate.vue @@ -66,7 +66,7 @@ >Set the type of dynamic policy for this collection. Read more about Dynamic Mappings. @@ -118,7 +118,7 @@ document in the collection (and its fields) are stored and indexed. Read more about mapping diff --git a/src/components/Security/Users/EditCustomMapping.vue b/src/components/Security/Users/EditCustomMapping.vue index 44ce25d5c..cd3cf80d4 100644 --- a/src/components/Security/Users/EditCustomMapping.vue +++ b/src/components/Security/Users/EditCustomMapping.vue @@ -47,7 +47,7 @@ Mapping is the process of defining how a document, and the fields it contains, are stored and indexed. Read more about mapping diff --git a/src/routes/index.ts b/src/routes/index.ts index de29bc53e..524ea7322 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -13,6 +13,7 @@ import DataLayout from '../components/Data/Layout.vue' import ResetPassword from '../components/ResetPassword.vue' import SecurityLayout from '../components/Security/Layout.vue' import PageNotFound from '../components/404.vue' +import ApiAction from '../components/ApiAction.vue' import SecuritySubRoutes from './children/security' import DataSubRoutes from './children/data' @@ -132,6 +133,11 @@ export default function createRoutes(log) { name: 'Security', component: SecurityLayout, children: SecuritySubRoutes + }, + { + path: '/api-action', + name: 'ApiAction', + component: ApiAction } ] } diff --git a/src/vuex/modules/auth/getters.ts b/src/vuex/modules/auth/getters.ts index 2eb13e1ed..c6eccbe10 100644 --- a/src/vuex/modules/auth/getters.ts +++ b/src/vuex/modules/auth/getters.ts @@ -46,6 +46,15 @@ const isActionAllowed = ( }) if ( + filteredRights.some(function(item) { + return item.value === 'allowed' + }) && + filteredRights.some(function(item) { + return item.value === 'denied' + }) + ) { + return false + } else if ( filteredRights.some(function(item) { return item.value === 'allowed' }) @@ -252,5 +261,13 @@ export const getters = createGetters()({ getters.canManageProfiles || getters.canManageUsers ) + }, + + // Server + canGetPublicApi(state) { + return isActionAllowed(state.user, 'server', 'publicApi') + }, + canGetOpenApi(state) { + return isActionAllowed(state.user, 'server', 'openapi') } }) diff --git a/test/e2e/cypress/integration/multi-backend/switch.spec.js b/test/e2e/cypress/integration/multi-backend/switch.spec.js index ed2e1f2ca..1e5f3ea2c 100644 --- a/test/e2e/cypress/integration/multi-backend/switch.spec.js +++ b/test/e2e/cypress/integration/multi-backend/switch.spec.js @@ -9,9 +9,10 @@ describe('Switch between two backends', () => { const indexName = 'testindex' backends.forEach(backend => { + cy.waitForService(`http://localhost:${backend.port}`, 'down') cy.task('doco', { version: backend.version, - docoArgs: ['up'], + docoArgs: ['up', '-d'], port: backend.port, stackPrefix: backend.name }) diff --git a/test/e2e/cypress/integration/single-backend/api-actions.spec.js b/test/e2e/cypress/integration/single-backend/api-actions.spec.js new file mode 100644 index 000000000..fd6a599ad --- /dev/null +++ b/test/e2e/cypress/integration/single-backend/api-actions.spec.js @@ -0,0 +1,328 @@ +describe('API Actions - query', function() { + const kuzzleUrl = 'http://localhost:7512' + + beforeEach(() => { + // reset database and setup + cy.request('POST', `${kuzzleUrl}/admin/_resetDatabase`) + cy.request('POST', `${kuzzleUrl}/admin/_resetSecurity`) + cy.initLocalEnv(Cypress.env('BACKEND_VERSION')) + }) + + it('Should be able to perform a query', () => { + const indexName = "testindex" + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('[data-cy="api-actions-query-JSONEditor-0"] textarea.ace_text-input') + .type(`{selectall}{backspace}{ +"controller": "index", +"action": "create", +"index": "${indexName}"`, { + delay: 200, + force: true + }) + cy.get('[data-cy="api-actions-run-button-0"]').click() + + cy.wait(1000) + + cy.request('GET', `${kuzzleUrl}/${indexName}/_exists`) + .should((response) => { + expect(response.body.result).to.equals(true) + }) + }) + + it('Should display the query status and response', () => { + const indexName = "testindex" + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.wait(500) + cy.get('[data-cy="api-actions-query-JSONEditor-0"] textarea.ace_text-input') + .type(`{selectall}{backspace}{ +"controller": "index", +"action": "create", +"index": "${indexName}"`, { + delay: 200, + force: true + }) + cy.get('[data-cy="api-actions-run-button-0"]').click() + + cy.wait(1000) + + cy.get('[data-cy="api-actions-response-status-0"]').should('contain', '200') + cy.get('[data-cy="api-actions-response-JSONEditor-0"]') + .should('contain', '"error": null') + .should('contain', '"action": "create"') + .should('contain', '"controller": "index"') + .should('contain', `"index": "${indexName}"`) + .should('contain', '"status": 200') + }) + + it('Should be able to set query controller using input', () => { + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('[data-cy="api-actions-controller-input-0"]').type("index") + cy.get('[data-cy="api-actions-query-JSONEditor-0"]') + .should('contain', '"controller": "index"') + }) + + it('Should be able to set query action using input', () => { + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('[data-cy="api-actions-action-input-0"]').type("create") + cy.get('[data-cy="api-actions-query-JSONEditor-0"]') + .should('contain', '"action": "create"') + }) + + it('Should be able to get query controllers available as options', () => { + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('#controllersList option') + .should('have.length.of.at.least', 1) + }) + + it('Should be able to get query actions available as options', () => { + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('[data-cy="api-actions-controller-input-0"]').type("index") + cy.get('#actionsList option') + .should('have.length.of.at.least', 1) + }) + + it('Should not display options if user has no rights (publicApi/openapi)', () => { + cy.waitOverlay() + cy.request({ + method: 'PUT', + url: 'http://localhost:7512/roles/anonymous', + body: { + controllers: { + '*': { + actions: { + '*': true + } + }, + server: { + actions: { + publicApi: false, + openapi: false + } + } + } + } + }) + cy.visit(`/#/api-action`) + cy.get('#controllersList option').should('have.length', 0) + cy.get('#actionsList option').should('have.length', 0) + }) +}) + +describe('API Actions - tabs and save', function() { + const kuzzleUrl = 'http://localhost:7512' + + beforeEach(() => { + // reset database and setup + cy.request('POST', `${kuzzleUrl}/admin/_resetDatabase`) + cy.request('POST', `${kuzzleUrl}/admin/_resetSecurity`) + cy.initLocalEnv(Cypress.env('BACKEND_VERSION')) + }) + + it('Should be able to open a new tab', () => { + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('ul[role="tablist"] li') + .should('have.length', 1) + cy.get('[data-cy="api-actions-tab-plus"]').click() + cy.get('ul[role="tablist"] li') + .should('have.length', 2) + }) + + it('Should persist query by tabs', () => { + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('[data-cy="api-actions-query-JSONEditor-0"] textarea.ace_text-input') + .type(`{selectall}{backspace}tab0`, { + delay: 200, + force: true + }) + cy.get('[data-cy="api-actions-tab-plus"]').click() + cy.get('[data-cy="api-actions-query-JSONEditor-1"]') + .should('not.contain', 'tab0') + cy.get('[data-cy="api-actions-tab-0"]').click() + cy.get('[data-cy="api-actions-query-JSONEditor-0"]') + .should('contain', 'tab0') + }) + + it('Should be able to save a new query', () => { + const queryName = "createIndexToto" + const envName = "valid" + cy.clearLocalStorage('storedQueries') + cy.waitOverlay() + cy.visit(`/#/api-action`) + cy.get('[data-cy="api-actions-query-JSONEditor-0"] textarea.ace_text-input') + .type(`{selectall}{backspace}{ +"controller": "index", +"action": "create", +"index": "toto"`, { + delay: 200, + force: true + }) + cy.get('[data-cy="api-actions-save-button-0"]').click() + cy.get('[data-cy="api-actions-modal-name-input"]') + .type(`{selectall}{backspace}${queryName}`, { + delay: 200, + force: true + }) + cy.get('[data-cy="api-actions-modal-ok-button"]').click() + cy.get(`[data-cy="api-actions-saved-query-${queryName}"]`) + cy.window().then( + window => { + const storedQueries = JSON.parse(window.localStorage.getItem('storedQueries')) + const queryIdx = storedQueries[envName].findIndex(e => e.name === queryName); + expect(queryIdx).to.equals(0) + expect(storedQueries[envName][queryIdx].query.controller).to.equals("index") + expect(storedQueries[envName][queryIdx].query.action).to.equals("create") + expect(storedQueries[envName][queryIdx].query.index).to.equals("toto") + } + ); + }) + + it('Should be able to open a saved query', () => { + const envName = 'valid' + const queryName = "createIndexToto" + const storedQueries = { + [envName]: [ + {"query":{"controller":"index","action":"create","index": "toto"},"name": queryName} + ] + } + localStorage.setItem('storedQueries', JSON.stringify(storedQueries)) + cy.waitOverlay() + cy.visit(`/#/api-action`) + + cy.get(`[data-cy="api-actions-saved-query-${queryName}"]`).click() + cy.get('[data-cy="api-actions-query-JSONEditor-1"]') + .should('contain', '"controller": "index"') + .should('contain', '"action": "create"') + .should('contain', '"index": "toto"') + }) + + it('Should be able to run a saved query', () => { + const envName = 'valid' + const queryName = "createIndexToto" + const indexName = 'toto' + const storedQueries = { + [envName]: [ + {"query":{"controller":"index","action":"create","index": indexName},"name": queryName} + ] + } + localStorage.setItem('storedQueries', JSON.stringify(storedQueries)) + cy.waitOverlay() + cy.visit(`/#/api-action`) + + cy.get(`[data-cy="api-actions-saved-query-${queryName}"]`).click() + cy.get('[data-cy="api-actions-run-button-1"]').click() + + cy.wait(1000) + + cy.request('GET', `${kuzzleUrl}/${indexName}/_exists`) + .should((response) => { + expect(response.body.result).to.equals(true) + }) + }) + + it('Should be able to edit a saved query', () => { + const envName = 'valid' + const queryName = "createIndexToto" + const indexName = 'toto' + const indexName2 = 'titi' + const storedQueries = { + [envName]: [ + {"query":{"controller":"index","action":"create","index": indexName},"name": queryName} + ] + } + localStorage.setItem('storedQueries', JSON.stringify(storedQueries)) + cy.waitOverlay() + cy.visit(`/#/api-action`) + + cy.get(`[data-cy="api-actions-saved-query-${queryName}"]`).click() + + cy.get('[data-cy="api-actions-query-JSONEditor-1"] textarea.ace_text-input') + .type(`{selectall}{backspace}{ +"controller": "index", +"action": "create", +"index": "${indexName2}"`, { + delay: 200, + force: true + }) + cy.get('[data-cy="api-actions-save-button-1"]').click() + + cy.window().then( + window => { + const storedQueries = JSON.parse(window.localStorage.getItem('storedQueries')) + const queryIdx = storedQueries[envName].findIndex(e => e.name === queryName); + expect(storedQueries[envName][queryIdx].query.index).to.equals(indexName2) + } + ); + }) + + it('Should be able to persist saved queries by environment', () => { + const envName = 'valid' + const envName2 = 'valid2' + const queryName = "createIndexToto" + const queryName2 = "createIndexTiti" + const indexName = 'toto' + const indexName2 = 'titi' + const backendVersion = Cypress.env('BACKEND_VERSION') || 2 + const storedQueries = { + [envName]: [ + {"query":{"controller":"index","action":"create","index": indexName},"name": queryName} + ], + [envName2]: [ + {"query":{"controller":"index","action":"create","index": indexName2},"name": queryName2} + ] + } + localStorage.setItem('storedQueries', JSON.stringify(storedQueries)) + localStorage.setItem( + 'environments', + JSON.stringify({ + [envName]: { + name: envName, + color: 'darkblue', + host: 'localhost', + ssl: false, + port: 7512, + backendMajorVersion: backendVersion, + token: null + }, + [envName2]: { + name: envName2, + color: 'darkblue', + host: 'localhost', + ssl: false, + port: 7512, + backendMajorVersion: backendVersion, + token: null + } + }) + ) + cy.waitOverlay() + cy.visit(`/#/login`) + cy.get('[data-cy="EnvironmentSwitch"]').click() + cy.get( + `[data-cy="EnvironmentSwitch-env_${envName}"] > .EnvironmentSwitch-env-name` + ).click({ + force: true + }) + cy.get('[data-cy="LoginAsAnonymous-Btn"]').click() + cy.visit(`/#/api-action`) + cy.get(`[data-cy="api-actions-saved-query-${queryName}"]`) + + cy.visit(`/#/login`) + cy.get('[data-cy="EnvironmentSwitch"]').click() + cy.get( + `[data-cy="EnvironmentSwitch-env_${envName2}"] > .EnvironmentSwitch-env-name` + ).click({ + force: true + }) + cy.get('[data-cy="LoginAsAnonymous-Btn"]').click() + cy.visit(`/#/api-action`) + cy.get(`[data-cy="api-actions-saved-query-${queryName2}"]`) + }) +}) diff --git a/test/e2e/cypress/integration/single-backend/collections.spec.js b/test/e2e/cypress/integration/single-backend/collections.spec.js index 8069da22b..2d8afb257 100644 --- a/test/e2e/cypress/integration/single-backend/collections.spec.js +++ b/test/e2e/cypress/integration/single-backend/collections.spec.js @@ -203,7 +203,6 @@ describe('Collection management', function() { it('Should disable delete stored collections for Kuzzle v1', () => { cy.skipUnlessBackendVersion(1) - cy.waitForService(`http://localhost:7512`) cy.request('PUT', `${kuzzleUrl}/${indexName}/${collectionName}`) cy.visit(`/#/data/`) diff --git a/test/e2e/cypress/integration/single-backend/environments.spec.js b/test/e2e/cypress/integration/single-backend/environments.spec.js index b8354e45b..df5672fbf 100644 --- a/test/e2e/cypress/integration/single-backend/environments.spec.js +++ b/test/e2e/cypress/integration/single-backend/environments.spec.js @@ -337,23 +337,22 @@ describe('Environments', function() { cy.get('[data-cy=App-offline]') .should('be.visible') .should('contain', 'Connecting to Kuzzle') - cy.task('doco', { version: backendVersion, docoArgs: ['up'] }) + cy.task('doco', { version: backendVersion, docoArgs: ['up', '-d'] }) cy.waitForService('http://localhost:7512') cy.get('[data-cy=App-online]').should('be.visible') }) it('Should display a toast when the backend goes down and hide it when the backend goes up again', () => { cy.initLocalEnv(backendVersion) - cy.task('doco', { version: backendVersion, docoArgs: ['up'] }) + cy.task('doco', { version: backendVersion, docoArgs: ['up', '-d'] }) cy.waitForService('http://localhost:7512') cy.visit('/') cy.get('[data-cy=App-online]').should('be.visible') cy.task('doco', { version: backendVersion, docoArgs: ['down'] }) - cy.wait(3000) cy.get('.toast-header') .should('be.visible') .should('contain', 'Offline') - cy.task('doco', { version: backendVersion, docoArgs: ['up'] }) + cy.task('doco', { version: backendVersion, docoArgs: ['up', '-d'] }) cy.waitForService('http://localhost:7512') cy.get('.toast-header').should('not.exist') }) @@ -504,7 +503,6 @@ describe('Import and export environments', function() { }, { subjectType: 'input', force: true } ) - .trigger('change') cy.get('[data-cy=EnvironmentImport-ok]') .should('exist') .should('contain', 'Found 2 connections') @@ -532,7 +530,6 @@ describe('Import and export environments', function() { }, { subjectType: 'input', force: true } ) - .trigger('change') cy.get('[data-cy=EnvironmentImport-err]') .should('exist') .should('contain', 'Uploaded file type (image/jpeg) is not supported.') diff --git a/test/e2e/cypress/integration/single-backend/formView.spec.js b/test/e2e/cypress/integration/single-backend/formView.spec.js index 2f4621050..8aab6f15c 100644 --- a/test/e2e/cypress/integration/single-backend/formView.spec.js +++ b/test/e2e/cypress/integration/single-backend/formView.spec.js @@ -46,7 +46,7 @@ describe('Form view', function() { }, skill: { name: 'managment', - level: '1' + level: 1 }, job: 'Always asking Esteban to do his job', employeeOfTheMonthSince: '1996-07-10' @@ -67,21 +67,20 @@ describe('Form view', function() { cy.get('input#age').type('31') cy.get('[name="items"] > textarea.ace_text-input') - .type('{backspace}{backspace}', { force: true }) - .type( - `{ - "desktop": "standing"`, + .type(`{selectall}{backspace}{ +"desktop": "standing"`, { + delay: 200, force: true } ) cy.get('[name="skill"] > textarea.ace_text-input') - .type('{backspace}{backspace}', { force: true }) - .type( - `{ - "name": "CSS", "level": 60`, + .type(`{selectall}{backspace}{ +"name": "CSS", +"level": 60`, { + delay: 200, force: true } ) @@ -126,15 +125,17 @@ describe('Form view', function() { .clear() .type('23:30:00') - cy.get('[name="skill"] > textarea.ace_text-input').type( - `{selectall}{backspace} - { - "name": "management", "level": 0`, - { - delay: 400, - force: true - } - ) + + + cy.get('[name="skill"] > textarea.ace_text-input') + .type(`{selectall}{backspace}{ +"name": "management", +"level": 0`, + { + delay: 200, + force: true + } + ) cy.get('[data-cy="DocumentUpdate-btn"').click({ force: true }) @@ -158,15 +159,10 @@ describe('Form view', function() { cy.get('[data-cy="DocumentListItem-update--testdoc"').click() cy.get('textarea.ace_text-input') - .type(`{selectall}{backspace}`, { - delay: 400, - force: true - }) - .type( - `{ - "name": "PHP CEO" -`, + .type(`{selectall}{backspace}{ +"name": "PHP CEO"`, { + delay: 200, force: true } ) diff --git a/test/e2e/cypress/plugins/index.js b/test/e2e/cypress/plugins/index.js index 81f5c06f2..110e041f4 100644 --- a/test/e2e/cypress/plugins/index.js +++ b/test/e2e/cypress/plugins/index.js @@ -15,7 +15,7 @@ const execa = require('execa') module.exports = on => { on('task', { - doco({ version, docoArgs, port, stackPrefix }) { + async doco({ version, docoArgs, port, stackPrefix }) { const docoFile = path.join( process.cwd(), 'test', @@ -29,12 +29,12 @@ module.exports = on => { stackPrefix = 'stack' } console.log( - `cy.task('doco') -- $ KUZZLE_PORT=${port} docker-compose -f ${docoFile} -p stack-${version} ${docoArgs.join( + `cy.task('doco') -- $ KUZZLE_PORT=${port} docker-compose -f ${docoFile} -p ${stackPrefix}-${version} ${docoArgs.join( ' ' )}` ) - execa( + await execa( 'docker-compose', ['-f', docoFile, '-p', `${stackPrefix}-${version}`].concat(docoArgs), { diff --git a/test/e2e/cypress/support/commands.js b/test/e2e/cypress/support/commands.js index 6ed19847a..db4c2280f 100644 --- a/test/e2e/cypress/support/commands.js +++ b/test/e2e/cypress/support/commands.js @@ -61,8 +61,8 @@ function wait(ms) { }) } -async function poll(url, state = 'up') { - for (let i = 30; i > 0; i--) { +async function poll(url, state = 'up', tries) { + for (let i = tries; i > 0; i--) { try { const r = await axios.get(url) if (r) { @@ -86,8 +86,8 @@ async function poll(url, state = 'up') { throw new Error('Poll timeout expired') } -Cypress.Commands.add('waitForService', (url, state = 'up') => { - return poll(url, state) +Cypress.Commands.add('waitForService', (url, state = 'up', tries = 30) => { + return poll(url, state, tries) }) Cypress.Commands.add('skipOnBackendVersion', version => { From eb161eb60025d2aa94c52834b37c23b4c24369c2 Mon Sep 17 00:00:00 2001 From: Berthier Esteban <44427849+berthieresteban@users.noreply.github.com> Date: Tue, 8 Jun 2021 09:55:17 +0200 Subject: [PATCH 06/16] fix broken links (#923) --- src/components/Common/ListNotAllowed.vue | 2 +- src/components/Common/PageNotAllowed.vue | 2 +- src/components/Data/Collections/Watch.vue | 2 +- src/components/Security/Common/Notice.vue | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Common/ListNotAllowed.vue b/src/components/Common/ListNotAllowed.vue index 305456ec0..809e5a65e 100644 --- a/src/components/Common/ListNotAllowed.vue +++ b/src/components/Common/ListNotAllowed.vue @@ -10,7 +10,7 @@ Learn more about security & permissions on the Kuzzle guideLearn more about security & permissions on Kuzzle guideLearn more about security & permissions on Kuzzle guidehttps://docs.kuzzle.io/core/2/guides/essentials/security + >https://docs.kuzzle.io/core/2/guides/main-concepts/permissions/ From 830ad175cca32928b93eb4a55f0268fead0c9829 Mon Sep 17 00:00:00 2001 From: Berthier Esteban <44427849+berthieresteban@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:04:14 +0200 Subject: [PATCH 07/16] bad translation (#920) --- src/components/Common/MainMenu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Common/MainMenu.vue b/src/components/Common/MainMenu.vue index e407422ec..ea170944b 100644 --- a/src/components/Common/MainMenu.vue +++ b/src/components/Common/MainMenu.vue @@ -96,7 +96,7 @@ export default { }, currentUserName() { if (!this.user) { - return 'Not authentified' + return 'Not authenticated' } if (this.user.id === -1) { return 'Anonymous' From 7547743dba4dfb9474e4512f5aadb79a671bf0b4 Mon Sep 17 00:00:00 2001 From: Berthier Esteban <44427849+berthieresteban@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:46:13 +0200 Subject: [PATCH 08/16] fix strategies displayed error (#925) --- src/services/kuzzleWrapper-v1.ts | 4 +++- test/e2e/cypress/integration/single-backend/users.spec.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/kuzzleWrapper-v1.ts b/src/services/kuzzleWrapper-v1.ts index dfa754c86..aca552d17 100644 --- a/src/services/kuzzleWrapper-v1.ts +++ b/src/services/kuzzleWrapper-v1.ts @@ -158,7 +158,9 @@ export class KuzzleWrapperV1 { ) formattedUser.credentials[strategy] = res } catch (e) { - formattedUser.credentials[strategy] = {} + /* eslint-disable no-empty */ + // Strategies contains local by default but some user + // might not have local credentials } } diff --git a/test/e2e/cypress/integration/single-backend/users.spec.js b/test/e2e/cypress/integration/single-backend/users.spec.js index 13cb2e26e..47f52735a 100644 --- a/test/e2e/cypress/integration/single-backend/users.spec.js +++ b/test/e2e/cypress/integration/single-backend/users.spec.js @@ -632,8 +632,9 @@ describe('Users', function() { cy.get('[data-cy=CredentialsSelector-local-password]').clear() cy.get('[data-cy="UserUpdate-submit"]').click({ force: true }) cy.get('[id="collapse-without-credentials"]') - .contains("local:") + .contains("credentials:") .next() + .should('not.contain', 'local:') .should("have.class", "json-formatter-empty") }) }) From adec9a055c907c0de02e98b70dc2ff317dd1f596 Mon Sep 17 00:00:00 2001 From: Berthier Esteban <44427849+berthieresteban@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:46:48 +0200 Subject: [PATCH 09/16] fix perpage change bug (#921) --- src/components/Common/CommonList.vue | 2 +- src/components/Common/PerPageSelector.vue | 36 +++++++++++++++++++ src/components/Data/Documents/Page.vue | 6 ++-- .../Data/Documents/Views/Column.vue | 25 ++++++------- src/components/Data/Documents/Views/List.vue | 27 ++++++-------- src/components/Data/Documents/Views/Map.vue | 23 +++++------- .../Data/Documents/Views/TimeSeries.vue | 23 +++++------- src/components/Security/Common/List.vue | 2 +- src/components/Security/Profiles/List.vue | 27 ++++++-------- src/components/Security/Roles/List.vue | 28 +++++++-------- src/components/Security/Users/List.vue | 27 ++++++-------- src/services/filterManager.ts | 2 +- .../single-backend/profiles.spec.js | 2 ++ .../integration/single-backend/roles.spec.js | 2 ++ .../integration/single-backend/search.spec.js | 1 + .../integration/single-backend/users.spec.js | 2 ++ 16 files changed, 123 insertions(+), 112 deletions(-) create mode 100644 src/components/Common/PerPageSelector.vue diff --git a/src/components/Common/CommonList.vue b/src/components/Common/CommonList.vue index 4ff0acfa7..38e51b6a4 100644 --- a/src/components/Common/CommonList.vue +++ b/src/components/Common/CommonList.vue @@ -113,7 +113,7 @@ export default { return parseInt(this.currentFilter.from) || 0 }, paginationSize() { - return parseInt(this.currentFilter.size) || 10 + return parseInt(this.currentFilter.size) || 25 } }, watch: { diff --git a/src/components/Common/PerPageSelector.vue b/src/components/Common/PerPageSelector.vue new file mode 100644 index 000000000..6e5f86e5d --- /dev/null +++ b/src/components/Common/PerPageSelector.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/src/components/Data/Documents/Page.vue b/src/components/Data/Documents/Page.vue index 625efb7bf..9e8e1e4a3 100644 --- a/src/components/Data/Documents/Page.vue +++ b/src/components/Data/Documents/Page.vue @@ -347,7 +347,7 @@ export default { return parseInt(this.currentFilter.from) || 0 }, paginationSize() { - return parseInt(this.currentFilter.size) || 10 + return parseInt(this.currentFilter.size) || 25 }, isRealtimeCollection() { return this.collection ? this.collection.isRealtime() : false @@ -644,7 +644,9 @@ export default { this.$log.debug(`changing pagination to ${size}`) this.onFiltersUpdated( Object.assign(this.currentFilter, { - size + size, + currentPage: 0, + from: 0 }) ) this.fetchDocuments() diff --git a/src/components/Data/Documents/Views/Column.vue b/src/components/Data/Documents/Views/Column.vue index 14dcaa228..f8d520e51 100644 --- a/src/components/Data/Documents/Views/Column.vue +++ b/src/components/Data/Documents/Views/Column.vue @@ -75,20 +75,13 @@ - Show - - - of {{ totalDocuments }} total items. + + + @@ -221,6 +214,7 @@ import { truncateName } from '@/utils' import { mapGetters } from 'vuex' import draggable from 'vuedraggable' import HeaderTableView from '../HeaderTableView' +import PerPageSelector from '@/components/Common/PerPageSelector' export default { name: 'Column', @@ -229,7 +223,8 @@ export default { }, components: { draggable, - HeaderTableView + HeaderTableView, + PerPageSelector }, props: { allChecked: Boolean, diff --git a/src/components/Data/Documents/Views/List.vue b/src/components/Data/Documents/Views/List.vue index 094e4859d..b8ac168c5 100644 --- a/src/components/Data/Documents/Views/List.vue +++ b/src/components/Data/Documents/Views/List.vue @@ -32,20 +32,13 @@ Refresh - Show - - - of {{ totalDocuments }} total items. + + + import DocumentListItem from '../DocumentListItem' import { mapGetters } from 'vuex' +import PerPageSelector from '@/components/Common/PerPageSelector' export default { name: 'DocumentsListView', components: { - DocumentListItem + DocumentListItem, + PerPageSelector }, props: { allChecked: { @@ -87,7 +82,7 @@ export default { }, currentPageSize: { type: Number, - default: 10 + default: 25 }, documents: { type: Array, diff --git a/src/components/Data/Documents/Views/Map.vue b/src/components/Data/Documents/Views/Map.vue index c38720f85..e55ec1232 100644 --- a/src/components/Data/Documents/Views/Map.vue +++ b/src/components/Data/Documents/Views/Map.vue @@ -13,18 +13,11 @@ - Show - - - - of {{ totalDocuments }} total items. - + @@ -136,13 +129,15 @@ import '@/assets/leaflet.css' import JsonFormatter from '@/directives/json-formatter.directive' import { mapGetters } from 'vuex' import _ from 'lodash' +import PerPageSelector from '@/components/Common/PerPageSelector' export default { name: 'ViewMap', components: { LMap, LTileLayer, - LMarker + LMarker, + PerPageSelector }, directives: { JsonFormatter @@ -150,7 +145,7 @@ export default { props: { currentPageSize: { type: Number, - default: 10 + default: 25 }, selectedGeopoint: { type: String, diff --git a/src/components/Data/Documents/Views/TimeSeries.vue b/src/components/Data/Documents/Views/TimeSeries.vue index c7e8d8ad2..f6312d552 100644 --- a/src/components/Data/Documents/Views/TimeSeries.vue +++ b/src/components/Data/Documents/Views/TimeSeries.vue @@ -3,18 +3,11 @@
- Show - - - of {{ totalDocuments }} total items. +
Date
- Show - - - of {{ totalDocuments }} total items. + + +
@@ -143,6 +136,7 @@ import DeleteModal from './DeleteModal' import ProfileItem from '../Profiles/ProfileItem' import Filters from './Filters' +import PerPageSelector from '@/components/Common/PerPageSelector' import { mapGetters } from 'vuex' export default { @@ -150,7 +144,8 @@ export default { components: { DeleteModal, Filters, - ProfileItem + ProfileItem, + PerPageSelector }, props: { index: String, @@ -173,7 +168,7 @@ export default { loading: true, selectedDocuments: [], totalDocuments: 0, - paginationSize: 10, + paginationSize: 25, itemsPerPage: [10, 25, 50, 100, 500] } }, diff --git a/src/components/Security/Roles/List.vue b/src/components/Security/Roles/List.vue index ec6a65c99..e5811fad3 100644 --- a/src/components/Security/Roles/List.vue +++ b/src/components/Security/Roles/List.vue @@ -61,20 +61,13 @@ Delete selected - Show - - - of {{ totalDocuments }} total items. + + +
@@ -119,14 +112,17 @@ import DeleteModal from './DeleteModal' import Filters from './Filters' import RoleItem from '../Roles/RoleItem' +import PerPageSelector from '@/components/Common/PerPageSelector' import * as filterManager from '../../../services/filterManager' import { mapGetters } from 'vuex' + export default { name: 'RoleList', components: { DeleteModal, Filters, - RoleItem + RoleItem, + PerPageSelector }, props: { displayCreate: { @@ -147,7 +143,7 @@ export default { loading: false, selectedDocuments: [], totalDocuments: 0, - paginationSize: 10, + paginationSize: 25, itemsPerPage: [10, 25, 50, 100, 500] } }, diff --git a/src/components/Security/Users/List.vue b/src/components/Security/Users/List.vue index bd552182a..c8f7d0a3c 100644 --- a/src/components/Security/Users/List.vue +++ b/src/components/Security/Users/List.vue @@ -89,20 +89,13 @@ Delete selected - Show - - - of {{ totalDocuments }} total items. + + +
{ cy.visit('/#/security/profiles') cy.contains('Profiles') + cy.get('[data-cy=ProfileItem]').should('have.length', 17) + cy.get('[data-cy=perPageSelector]').select('10') cy.get('[data-cy=ProfileItem]').should('have.length', 10) cy.get( '[data-cy="ProfileManagement-pagination"] .page-link[aria-posinset="2"]' diff --git a/test/e2e/cypress/integration/single-backend/roles.spec.js b/test/e2e/cypress/integration/single-backend/roles.spec.js index 4d6469b63..768cbf614 100644 --- a/test/e2e/cypress/integration/single-backend/roles.spec.js +++ b/test/e2e/cypress/integration/single-backend/roles.spec.js @@ -293,6 +293,8 @@ describe('Roles', () => { } cy.visit('#/security/roles') cy.contains('Roles') + cy.get('[data-cy="RoleItem"]').should('have.length', 17) + cy.get('[data-cy=perPageSelector]').select('10') cy.get('[data-cy="RoleItem"]').should('have.length', 10) cy.get( '[data-cy="RolesManagement-pagination"] .page-link[aria-posinset="2"]' diff --git a/test/e2e/cypress/integration/single-backend/search.spec.js b/test/e2e/cypress/integration/single-backend/search.spec.js index d5e032d03..92220f00a 100644 --- a/test/e2e/cypress/integration/single-backend/search.spec.js +++ b/test/e2e/cypress/integration/single-backend/search.spec.js @@ -623,6 +623,7 @@ describe('Search', function() { cy.get('[data-cy=BasicFilter-sortAttributeSelect]').select('lastName') cy.get('[data-cy=BasicFilter-submitBtn]').click() + cy.get('[data-cy=perPageSelector]').select('10') cy.get('[data-cy=DocumentList-pagination] [aria-posinset=4]').click() diff --git a/test/e2e/cypress/integration/single-backend/users.spec.js b/test/e2e/cypress/integration/single-backend/users.spec.js index 47f52735a..ce0b36038 100644 --- a/test/e2e/cypress/integration/single-backend/users.spec.js +++ b/test/e2e/cypress/integration/single-backend/users.spec.js @@ -275,6 +275,8 @@ describe('Users', function() { ) } cy.visit('/#/security/users') + cy.get('[data-cy=UserItem]').should('have.length', 14) + cy.get('[data-cy=perPageSelector]').select('10') cy.get('[data-cy=UserItem]').should('have.length', 10) cy.get( From 3b5a391d240fb67090ac7ac14358bbaf6ca2cac7 Mon Sep 17 00:00:00 2001 From: Berthier Esteban <44427849+berthieresteban@users.noreply.github.com> Date: Fri, 11 Jun 2021 15:37:05 +0200 Subject: [PATCH 10/16] fix bug delete history search (#924) --- .../Common/Filters/FilterHistoryItem.vue | 1 + .../Common/Filters/HistoryFilter.vue | 5 ++++ .../integration/single-backend/search.spec.js | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/components/Common/Filters/FilterHistoryItem.vue b/src/components/Common/Filters/FilterHistoryItem.vue index eb131dd94..31bea31aa 100644 --- a/src/components/Common/Filters/FilterHistoryItem.vue +++ b/src/components/Common/Filters/FilterHistoryItem.vue @@ -46,6 +46,7 @@ diff --git a/src/components/Common/Filters/HistoryFilter.vue b/src/components/Common/Filters/HistoryFilter.vue index 434db04fc..61b137c62 100644 --- a/src/components/Common/Filters/HistoryFilter.vue +++ b/src/components/Common/Filters/HistoryFilter.vue @@ -75,6 +75,11 @@ export default { }) .indexOf(id) this.filters.splice(idIndex, 1) + filterManager.saveHistoyToLocalStorage( + this.filters, + this.index, + this.collection + ) }, toggleFavorite(filter, favorite) { if (favorite) { diff --git a/test/e2e/cypress/integration/single-backend/search.spec.js b/test/e2e/cypress/integration/single-backend/search.spec.js index 92220f00a..01d7d433a 100644 --- a/test/e2e/cypress/integration/single-backend/search.spec.js +++ b/test/e2e/cypress/integration/single-backend/search.spec.js @@ -703,6 +703,35 @@ describe('Search', function() { } }) + it('Should be able to delete a previous search in the search history', () => { + cy.request( + 'POST', + `${kuzzleUrl}/${indexName}/${collectionName}/dummy-0/_create?refresh=wait_for`, + { + firstName: 'Dummy', + lastName: `Clone-0`, + job: 'Blockchain as a Service' + } + ) + cy.visit(`/#/data/${indexName}/${collectionName}`) + + cy.get('[data-cy=QuickFilter-optionBtn]').click() + cy.get('[data-cy=Filters-basicTab]').click() + cy.get('[data-cy="BasicFilter-attributeSelect--0.0"]').select('lastName') + cy.get('[data-cy="BasicFilter-valueInput--0.0"]').type(0) + cy.get('[data-cy=BasicFilter-submitBtn]').click() + cy.get('[data-cy=QuickFilter-displayActiveFilters]').click() + + cy.get('[data-cy=Filters-historyTab]').click() + cy.get('[data-cy="FilterHistoryItem--0"]') + cy.get('[data-cy="FilterHistoryItem-deleteBtn--0"]').click() + cy.get('[data-cy="FilterHistoryItem--0"]').should('not.exist') + cy.visit(`/#/data/${indexName}/${collectionName}`) + cy.get('[data-cy=QuickFilter-displayActiveFilters]').click() + cy.get('[data-cy=Filters-historyTab]').click() + cy.get('[data-cy="FilterHistoryItem--0"]').should('not.exist') + }) + it('should be able to add a filter to favorite', () => { const docCount = 10 const documents = [] From 0420a32dd1d3c84ec2ae5ef3b3699832498fcb3c Mon Sep 17 00:00:00 2001 From: Michele Leo Date: Fri, 18 Jun 2021 08:33:27 +0200 Subject: [PATCH 11/16] Fix broken doc links (#928) --- src/components/Common/ListNotAllowed.vue | 2 +- src/components/Common/PageNotAllowed.vue | 2 +- src/components/Data/Collections/Watch.vue | 2 +- src/components/Security/Common/Notice.vue | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Common/ListNotAllowed.vue b/src/components/Common/ListNotAllowed.vue index 809e5a65e..12016f58e 100644 --- a/src/components/Common/ListNotAllowed.vue +++ b/src/components/Common/ListNotAllowed.vue @@ -10,7 +10,7 @@ Learn more about security & permissions on the Kuzzle guideLearn more about security & permissions on Kuzzle guideLearn more about security & permissions on Kuzzle guidehttps://docs.kuzzle.io/core/2/guides/main-concepts/permissions/ Date: Wed, 23 Jun 2021 09:17:12 +0200 Subject: [PATCH 12/16] enhancement(profile-search): increase profile search count (#930) --- src/services/kuzzleWrapper-v1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/kuzzleWrapper-v1.ts b/src/services/kuzzleWrapper-v1.ts index aca552d17..145645b70 100644 --- a/src/services/kuzzleWrapper-v1.ts +++ b/src/services/kuzzleWrapper-v1.ts @@ -205,7 +205,7 @@ export class KuzzleWrapperV1 { async performSearchProfiles(filters = {}, pagination = {}) { const result = await this.kuzzle.security.searchProfiles( { ...filters }, - { size: 100, ...pagination } + { size: 1000, ...pagination } ) const profiles = result.hits.map(document => { From a71e4a5867cb2206ce59d814d91ce89a5360d09d Mon Sep 17 00:00:00 2001 From: Berthier Esteban <44427849+berthieresteban@users.noreply.github.com> Date: Fri, 2 Jul 2021 17:20:13 +0200 Subject: [PATCH 13/16] Type in profile policy --- src/components/Security/Profiles/CreateOrUpdate.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Security/Profiles/CreateOrUpdate.vue b/src/components/Security/Profiles/CreateOrUpdate.vue index 905309d69..64fc5cf40 100644 --- a/src/components/Security/Profiles/CreateOrUpdate.vue +++ b/src/components/Security/Profiles/CreateOrUpdate.vue @@ -65,7 +65,7 @@
 {
   "policies": [{
-      "roleIds": "roleId"
+      "roleId": "roleId"
       "restrictedTo": {
         "index": "myindex",
         "collections": [

From 31659726e7196872d5a61e15d6c55b52b107e15b Mon Sep 17 00:00:00 2001
From: Michele Leo 
Date: Fri, 2 Jul 2021 17:24:41 +0200
Subject: [PATCH 14/16] Display local strategy usernames if found (#927)

Display local strategy usernames if found
---
 src/components/Security/Users/UserItem.vue    | 210 +++++++++---------
 .../integration/single-backend/users.spec.js  |  32 ++-
 2 files changed, 134 insertions(+), 108 deletions(-)

diff --git a/src/components/Security/Users/UserItem.vue b/src/components/Security/Users/UserItem.vue
index f9e4d35f7..348f98cbd 100644
--- a/src/components/Security/Users/UserItem.vue
+++ b/src/components/Security/Users/UserItem.vue
@@ -1,19 +1,14 @@
 
 
 
 
 
diff --git a/test/e2e/cypress/integration/single-backend/users.spec.js b/test/e2e/cypress/integration/single-backend/users.spec.js
index ce0b36038..e2c3230c2 100644
--- a/test/e2e/cypress/integration/single-backend/users.spec.js
+++ b/test/e2e/cypress/integration/single-backend/users.spec.js
@@ -625,18 +625,40 @@ describe('Users', function() {
     })
   })
 
+  it('Should display the username if it is present in the local strategy', () => {
+    const username = 'Estebaaaan'
+
+    cy.request('POST', `${kuzzleUrl}/users/_create?refresh=wait_for`, {
+      content: {
+        profileIds: ['default'],
+        name: username
+      },
+      credentials: {
+        local: {
+          username: username,
+          password: 'test'
+        }
+      }
+    })
+
+    cy.visit('/#/security/users')
+    cy.get(`[data-cy="local-strategy-username-${username}"]`)
+      .should('be.visible')
+      .should('contain', username)
+  })
+
   it('Should be able to create an user without strategy', () => {
     cy.visit(`/#/security/users/create`)
 
-    cy.get('[data-cy=UserBasic-kuid]').type("without-credentials")
+    cy.get('[data-cy=UserBasic-kuid]').type('without-credentials')
     cy.get('[data-cy="UserProfileList-select"]').select('admin')
     cy.get('[data-cy=CredentialsSelector-local-username]').clear()
     cy.get('[data-cy=CredentialsSelector-local-password]').clear()
     cy.get('[data-cy="UserUpdate-submit"]').click({ force: true })
     cy.get('[id="collapse-without-credentials"]')
-    .contains("credentials:")
-    .next()
-    .should('not.contain', 'local:')
-    .should("have.class", "json-formatter-empty")
+      .contains('credentials:')
+      .next()
+      .should('not.contain', 'local:')
+      .should('have.class', 'json-formatter-empty')
   })
 })

From d3aa5295856fb9b921340eede0686efc3a1a93fa Mon Sep 17 00:00:00 2001
From: Luca Marchesini 
Date: Mon, 5 Jul 2021 15:24:08 +0200
Subject: [PATCH 15/16] Bump minor version

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4a29309c9..4ed301a1c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "kuzzle-admin-console",
-  "version": "4.0.0-beta",
+  "version": "4.1.0",
   "description": "A handy administrative console for Kuzzle",
   "author": "The Kuzzle team ",
   "scripts": {

From 3f9dc3580d09059eaea692badf3b5ad248014699 Mon Sep 17 00:00:00 2001
From: Luca Marchesini 
Date: Mon, 5 Jul 2021 15:33:44 +0200
Subject: [PATCH 16/16] Release 4.2.0

---
 package-lock.json | 2 +-
 package.json      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 1a05c3add..cf55df1ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "kuzzle-admin-console",
-  "version": "4.0.0-beta",
+  "version": "4.2.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 4ed301a1c..5eec575cd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "kuzzle-admin-console",
-  "version": "4.1.0",
+  "version": "4.2.0",
   "description": "A handy administrative console for Kuzzle",
   "author": "The Kuzzle team ",
   "scripts": {