diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index dd3081822e..9831df7f30 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -38,8 +38,12 @@ if __name__ == "__main__": pr_number = os.environ.get("PR_NUMBER") repo = os.environ.get("REPO_NAME") - if not files_list and pr_number: - files_list = get_files_list(pr_number=pr_number, repo=repo) + # this is a push build, run all builds + if not pr_number: + os.system('echo "::set-output name=build::strawberry"') + sys.exit(0) + + files_list = files_list or get_files_list(pr_number=pr_number, repo=repo) if not files_list: print("No files' changes detected. Build is shutting") diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 6ccc059afb..0dd4cd51d8 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -2,6 +2,11 @@ name: Patch on: [pull_request, workflow_dispatch] + +concurrency: + group: patch-mariadb-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index a3cd5d7ff1..fb6e56037c 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -6,6 +6,11 @@ on: push: branches: [ develop ] +concurrency: + group: server-mariadb-develop-${{ github.event.number }} + cancel-in-progress: true + + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 17a0f6f94f..1539e8c2d5 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -4,6 +4,10 @@ on: pull_request: workflow_dispatch: +concurrency: + group: server-postgres-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index d56433c216..2a55546ec4 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -6,6 +6,10 @@ on: push: branches: [ develop ] +concurrency: + group: ui-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/cypress/integration/datetime_field_form_validation.js b/cypress/integration/datetime_field_form_validation.js new file mode 100644 index 0000000000..66fdde6863 --- /dev/null +++ b/cypress/integration/datetime_field_form_validation.js @@ -0,0 +1,19 @@ +context('Datetime Field Validation', () => { + before(() => { + cy.login(); + cy.visit('/app/communication'); + cy.window().its('frappe').then(frappe => { + frappe.call("frappe.tests.ui_test_helpers.create_communication_records"); + }); + }); + + // validating datetime field value when value is set from backend and get validated on form load. + it('datetime field form validation', () => { + cy.visit('/app/communication'); + cy.get('a[title="Test Form Communication 1"]').invoke('attr', 'data-name') + .then((name) => { + cy.visit(`/app/communication/${name}`); + cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); + }); + }); +}); \ No newline at end of file diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js new file mode 100644 index 0000000000..1b7c02d98c --- /dev/null +++ b/cypress/integration/folder_navigation.js @@ -0,0 +1,79 @@ +context('Folder Navigation', () => { + before(() => { + cy.visit('/login'); + cy.login(); + cy.visit('/app/file'); + }); + + it('Adding Folders', () => { + //Adding filter to go into the home folder + cy.get('.filter-selector > .btn').findByText('1 filter').click(); + cy.findByRole('button', {name: 'Clear Filters'}).click(); + cy.get('.filter-action-buttons > .text-muted').findByText('+ Add a Filter').click(); + cy.get('.fieldname-select-area > .awesomplete > .form-control').type('Fol{enter}'); + cy.get('.filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback').type('Home{enter}'); + cy.get('.filter-action-buttons > div > .btn-primary').findByText('Apply Filters').click(); + + //Adding folder (Test Folder) + cy.get('.menu-btn-group > .btn').click(); + cy.get('.menu-btn-group [data-label="New Folder"]').click(); + cy.get('form > [data-fieldname="value"]').type('Test Folder'); + cy.findByRole('button', {name: 'Create'}).click(); + }); + + it('Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct', () => { + //Navigating inside the Attachments folder + cy.get('[title="Attachments"] > span').click(); + + //To check if the URL formed after visiting the attachments folder is correct + cy.location('pathname').should('eq', '/app/file/view/home/Attachments'); + cy.visit('/app/file/view/home/Attachments'); + + //Adding folder inside the attachments folder + cy.get('.menu-btn-group > .btn').click(); + cy.get('.menu-btn-group [data-label="New Folder"]').click(); + cy.get('form > [data-fieldname="value"]').type('Test Folder'); + cy.findByRole('button', {name: 'Create'}).click(); + + //Navigating inside the added folder in the Attachments folder + cy.get('[title="Test Folder"] > span').click(); + + //To check if the URL is correct after visiting the Test Folder + cy.location('pathname').should('eq', '/app/file/view/home/Attachments/Test%20Folder'); + cy.visit('/app/file/view/home/Attachments/Test%20Folder'); + + //Adding a file inside the Test Folder + cy.findByRole('button', {name: 'Add File'}).eq(0).click({force: true}); + cy.get('.file-uploader').findByText('Link').click(); + cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); + cy.findByRole('button', {name: 'Upload'}).click(); + + //To check if the added file is present in the Test Folder + cy.get('span.level-item > span').should('contain', 'Test Folder'); + cy.get('.list-row-container').eq(0).should('contain.text', '72402.jpg'); + cy.get('.list-row-checkbox').eq(0).click(); + + //Deleting the added file from the Test folder + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); + cy.wait(700); + cy.findByRole('button', {name: 'Yes'}).click(); + cy.wait(700); + + //Deleting the Test Folder + cy.visit('/app/file/view/home/Attachments'); + cy.get('.list-row-checkbox').eq(0).click(); + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); + cy.findByRole('button', {name: 'Yes'}).click(); + }); + + it('Deleting Test Folder from the home', () => { + //Deleting the Test Folder added in the home directory + cy.visit('/app/file/view/home'); + cy.get('.level-left > .list-subject > .list-row-checkbox').eq(0).click({force: true, delay: 500}); + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); + cy.findByRole('button', {name: 'Yes'}).click(); + }); +}); diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index b42c9c89a0..610eaf466a 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -391,7 +391,7 @@ def handle_duration_fieldtype_values(result, columns): return result -def build_xlsx_data(columns, data, visible_idx, include_indentation): +def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visible_idx=False): result = [[]] column_widths = [] @@ -407,7 +407,7 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation): # build table from result for row_idx, row in enumerate(data.result): # only pick up rows that are visible in the report - if row_idx in visible_idx: + if ignore_visible_idx or row_idx in visible_idx: row_data = [] if isinstance(row, dict): for col_idx, column in enumerate(data.columns): diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index f30279e308..ccfff594b7 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -13,6 +13,7 @@ from frappe.utils import (format_time, get_link_to_form, get_url_to_report, from frappe.model.naming import append_number_if_name_exists from frappe.utils.csvutils import to_csv from frappe.utils.xlsxutils import make_xlsx +from frappe.desk.query_report import build_xlsx_data max_reports_per_user = frappe.local.conf.max_reports_per_user or 3 @@ -99,13 +100,21 @@ class AutoEmailReport(Document): return self.get_html_table(columns, data) elif self.format == 'XLSX': - spreadsheet_data = self.get_spreadsheet_data(columns, data) - xlsx_file = make_xlsx(spreadsheet_data, "Auto Email Report") + report_data = frappe._dict() + report_data['columns'] = columns + report_data['result'] = data + + xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True) + xlsx_file = make_xlsx(xlsx_data, "Auto Email Report", column_widths=column_widths) return xlsx_file.getvalue() elif self.format == 'CSV': - spreadsheet_data = self.get_spreadsheet_data(columns, data) - return to_csv(spreadsheet_data) + report_data = frappe._dict() + report_data['columns'] = columns + report_data['result'] = data + + xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True) + return to_csv(xlsx_data) else: frappe.throw(_('Invalid Output Format')) @@ -126,18 +135,6 @@ class AutoEmailReport(Document): 'edit_report_settings': get_link_to_form('Auto Email Report', self.name) }) - @staticmethod - def get_spreadsheet_data(columns, data): - out = [[_(df.label) for df in columns], ] - for row in data: - new_row = [] - out.append(new_row) - for df in columns: - if df.fieldname not in row: continue - new_row.append(frappe.format(row[df.fieldname], df, row)) - - return out - def get_file_name(self): return "{0}.{1}".format(self.report.replace(" ", "-").replace("/", "-"), self.format.lower()) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 752543f46a..5603b2daae 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -874,7 +874,7 @@ class BaseDocument(object): return self._precision[cache_key][fieldname] - def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False): + def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False, format=None): from frappe.utils.formatters import format_value df = self.meta.get_field(fieldname) @@ -898,7 +898,7 @@ class BaseDocument(object): if (absolute_value or doc.get('absolute_value')) and isinstance(val, (int, float)): val = abs(self.get(fieldname)) - return format_value(val, df=df, doc=doc, currency=currency) + return format_value(val, df=df, doc=doc, currency=currency, format=format) def is_print_hide(self, fieldname, df=None, for_print=True): """Returns true if fieldname is to be hidden for print. diff --git a/frappe/public/js/frappe/data_import/data_exporter.js b/frappe/public/js/frappe/data_import/data_exporter.js index 03e6288856..8fa5a08945 100644 --- a/frappe/public/js/frappe/data_import/data_exporter.js +++ b/frappe/public/js/frappe/data_import/data_exporter.js @@ -13,6 +13,13 @@ frappe.data_import.DataExporter = class DataExporter { this.dialog = new frappe.ui.Dialog({ title: __('Export Data'), fields: [ + { + fieldtype: 'Select', + fieldname: 'file_type', + label: __('File Type'), + options: ['Excel', 'CSV'], + default: 'CSV' + }, { fieldtype: 'Select', fieldname: 'export_records', @@ -45,13 +52,6 @@ frappe.data_import.DataExporter = class DataExporter { fieldname: 'filter_area', depends_on: doc => doc.export_records === 'by_filter' }, - { - fieldtype: 'Select', - fieldname: 'file_type', - label: __('File Type'), - options: ['Excel', 'CSV'], - default: 'CSV' - }, { fieldtype: 'Section Break' }, @@ -141,7 +141,7 @@ frappe.data_import.DataExporter = class DataExporter { let for_insert = this.exporting_for === 'Insert New Records'; let section_title = for_insert ? __('Select Fields To Insert') : __('Select Fields To Update'); let $select_all_buttons = $(` -