diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.70.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.70.0.tgz deleted file mode 100644 index b36a258f1c..0000000000 Binary files a/web/api/js/codechecker-api-node/dist/codechecker-api-6.70.0.tgz and /dev/null differ diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.71.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.71.0.tgz new file mode 100644 index 0000000000..ef5042ea71 Binary files /dev/null and b/web/api/js/codechecker-api-node/dist/codechecker-api-6.71.0.tgz differ diff --git a/web/api/js/codechecker-api-node/package.json b/web/api/js/codechecker-api-node/package.json index b47d543df9..9290828ae1 100644 --- a/web/api/js/codechecker-api-node/package.json +++ b/web/api/js/codechecker-api-node/package.json @@ -1,6 +1,6 @@ { "name": "codechecker-api", - "version": "6.70.0", + "version": "6.71.0", "description": "Generated node.js compatible API stubs for CodeChecker server.", "main": "lib", "homepage": "https://github.com/Ericsson/codechecker", diff --git a/web/api/py/codechecker_api/dist/codechecker_api.tar.gz b/web/api/py/codechecker_api/dist/codechecker_api.tar.gz index ad881dbcc1..8ef03a3507 100644 Binary files a/web/api/py/codechecker_api/dist/codechecker_api.tar.gz and b/web/api/py/codechecker_api/dist/codechecker_api.tar.gz differ diff --git a/web/api/py/codechecker_api/setup.py b/web/api/py/codechecker_api/setup.py index ecf0d97ee1..5fa51b614d 100644 --- a/web/api/py/codechecker_api/setup.py +++ b/web/api/py/codechecker_api/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.70.0' +api_version = '6.71.0' setup( name='codechecker_api', diff --git a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz index a93cf41931..f8e377583e 100644 Binary files a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz and b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz differ diff --git a/web/api/py/codechecker_api_shared/setup.py b/web/api/py/codechecker_api_shared/setup.py index f789554aa4..fa9541f015 100644 --- a/web/api/py/codechecker_api_shared/setup.py +++ b/web/api/py/codechecker_api_shared/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.70.0' +api_version = '6.71.0' setup( name='codechecker_api_shared', diff --git a/web/api/report_server.thrift b/web/api/report_server.thrift index 90ef38027e..08ecd3d112 100644 --- a/web/api/report_server.thrift +++ b/web/api/report_server.thrift @@ -607,8 +607,15 @@ service codeCheckerDBAccess { i64 storeFilterPreset(1: FilterPreset preset) throws (1: codechecker_api_shared.RequestFailed requestError); + // Returns: the id of the renamed preset + // Throws an error in case there is no preset with the given id + // PERMISSION: PRODUCT_ADMIN + i64 renameFilterPreset(1: i64 id + 2: string name) + throws (1: codechecker_api_shared.RequestFailed requestError); + // Returns the "FilterPreset" identified by id - // Throws and error in case there is no preset with the given id + // Throws an error in case there is no preset with the given id // PERMISSION: PRODUCT_VIEW FilterPreset getFilterPreset(1: i64 id) throws (1: codechecker_api_shared.RequestFailed requestError); diff --git a/web/client/codechecker_client/cli/cmd.py b/web/client/codechecker_client/cli/cmd.py index ca032632b6..67eeb5414a 100644 --- a/web/client/codechecker_client/cli/cmd.py +++ b/web/client/codechecker_client/cli/cmd.py @@ -1588,15 +1588,6 @@ def __register_delete(parser): subcommands = parser.add_subparsers(title='available actions') # Create handlers for individual subcommands. - list_presets = subcommands.add_parser( - 'list', - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="List all filter presets available on the server.", - help="List all filter presets.") - list_presets.set_defaults(func=filter_preset_client.handle_list_presets) - __add_common_arguments(list_presets, - output_formats=DEFAULT_OUTPUT_FORMATS) - new_preset = subcommands.add_parser( 'new', formatter_class=arg.RawDescriptionDefaultHelpFormatter, @@ -1606,6 +1597,34 @@ def __register_delete(parser): new_preset.set_defaults(func=filter_preset_client.handle_new_preset) __add_common_arguments(new_preset) + rename_preset = subcommands.add_parser( + 'rename', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Rename an existing filter preset.", + help="Rename an existing filter preset.") + rename_preset.set_defaults(func=filter_preset_client.handle_rename_preset) + rename_preset.add_argument( + '--preset-id', + type=int, + required=True, + help="ID of the filter preset to rename.") + rename_preset.add_argument( + '--new-name', + type=str, + required=True, + help="New name for the filter preset.") + __add_common_arguments(rename_preset, + output_formats=DEFAULT_OUTPUT_FORMATS) + + list_presets = subcommands.add_parser( + 'list', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="List all filter presets available on the server.", + help="List all filter presets.") + list_presets.set_defaults(func=filter_preset_client.handle_list_presets) + __add_common_arguments(list_presets, + output_formats=DEFAULT_OUTPUT_FORMATS) + delete_preset = subcommands.add_parser( 'delete', formatter_class=argparse.ArgumentDefaultsHelpFormatter, diff --git a/web/client/codechecker_client/filter_preset_client.py b/web/client/codechecker_client/filter_preset_client.py index 61ecbf6988..d53c596c12 100644 --- a/web/client/codechecker_client/filter_preset_client.py +++ b/web/client/codechecker_client/filter_preset_client.py @@ -285,6 +285,23 @@ def handle_new_preset(args): sys.exit(1) +def handle_rename_preset(args): + """ + Handler for renaming a filter preset. + """ + init_logger(args.verbose if 'verbose' in args else None) + + client = setup_client(args.product_url) + + try: + client.renameFilterPreset(args.preset_id, args.new_name) + LOG.info("Filter preset (ID: %d) renamed to '%s'.", + args.preset_id, args.new_name) + except Exception as e: + LOG.error("An error occurred while renaming the filter preset: %s", e) + sys.exit(1) + + def handle_list_presets(args): """ Handler for listing all filter presets. diff --git a/web/client/codechecker_client/helpers/results.py b/web/client/codechecker_client/helpers/results.py index 4285b5db4b..ab698b3c3f 100644 --- a/web/client/codechecker_client/helpers/results.py +++ b/web/client/codechecker_client/helpers/results.py @@ -180,6 +180,10 @@ def removeSourceComponent(self, name): def storeFilterPreset(self, preset): pass + @thrift_client_call + def renameFilterPreset(self, id, name): + pass + @thrift_client_call def getFilterPreset(self, id): pass diff --git a/web/codechecker_web/shared/version.py b/web/codechecker_web/shared/version.py index 75d538edef..96652ab252 100644 --- a/web/codechecker_web/shared/version.py +++ b/web/codechecker_web/shared/version.py @@ -20,7 +20,7 @@ # The newest supported minor version (value) for each supported major version # (key) in this particular build. SUPPORTED_VERSIONS = { - 6: 70 + 6: 71 } # Used by the client to automatically identify the latest major and minor diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py index 217a613eb1..582b799a42 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -1672,7 +1672,7 @@ def storeFilterPreset(self, filterpreset): LOG.info("Storing filter preset in backend: %s", filterpreset.name) try: filter_id = filterpreset.id - name = filterpreset.name + name = filterpreset.name.strip() report_filter = json.dumps( thrift_to_json(filterpreset.reportFilter)) @@ -1717,6 +1717,65 @@ def storeFilterPreset(self, filterpreset): codechecker_api_shared.ttypes.ErrorCode.DATABASE, "CodeChecker could not store the filter preset: " + str(ex)) + @exc_to_thrift_reqfail + @timeit + def renameFilterPreset(self, preset_id: int, name: str): + """ + Rename a filter preset. + Returns the ID of the renamed preset. + Raises an error if id/name is empty or if + a preset with the new name already exists. + """ + self.__require_admin() + try: + with DBSession(self._Session) as session: + + name = name.strip() + + if not name: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + "Preset name cannot be empty!") + + if not preset_id: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + "Preset ID cannot be empty!") + + preset_entry = session.query(FilterPreset).filter( + FilterPreset.id == preset_id + ).one_or_none() + + if not preset_entry: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + f"No filter preset found with id {preset_id}!") + + existing = session.query(FilterPreset).filter( + FilterPreset.preset_name == name + ).one_or_none() + + if existing and existing.id != preset_id: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + f"A filter preset with name " + f"'{name}' already exists!") + + LOG.info("Renaming filter preset { id: %d, name: %s } " + "with name: '%s' ", + preset_entry.id, preset_entry.preset_name, + name) + + preset_entry.preset_name = name + session.commit() + return preset_entry.id + + except Exception as ex: + session.rollback() + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.DATABASE, + "CodeChecker could not rename filter preset: " + str(ex)) + @exc_to_thrift_reqfail @timeit def deleteFilterPreset(self, preset_id): diff --git a/web/server/vue-cli/package-lock.json b/web/server/vue-cli/package-lock.json index c3f7257159..8d428577bc 100644 --- a/web/server/vue-cli/package-lock.json +++ b/web/server/vue-cli/package-lock.json @@ -12,7 +12,7 @@ "@mdi/font": "^6.5.95", "chart.js": "^4.0.0", "chartjs-plugin-datalabels": "^2.0.0", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.70.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.71.0.tgz", "codemirror": "^6.0.2", "date-fns": "^2.28.0", "dompurify": "^3.3.1", @@ -6003,9 +6003,9 @@ } }, "node_modules/codechecker-api": { - "version": "6.70.0", - "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.70.0.tgz", - "integrity": "sha512-88e5fv9XXWZQC4x8o6FbR9PyLZU5406U0nguPRZFvYa6lZRoAFLl88VC4RO7f/Ga/UC7Xw+qKcYV+6iSaFHatQ==", + "version": "6.71.0", + "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.71.0.tgz", + "integrity": "sha512-ZPQGZTctTH1qM2/3CNf/UEeNF6Kxz5yhTaxGFhpdF+KqzQ+WnZrzXrxWu319UZvcItTxTGrk/VOiBkwUZVlzYA==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "thrift": "0.13.0-hotfix.1" diff --git a/web/server/vue-cli/package.json b/web/server/vue-cli/package.json index 2e5074d450..ae0f1e24db 100644 --- a/web/server/vue-cli/package.json +++ b/web/server/vue-cli/package.json @@ -30,7 +30,7 @@ "@mdi/font": "^6.5.95", "chart.js": "^4.0.0", "chartjs-plugin-datalabels": "^2.0.0", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.70.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.71.0.tgz", "codemirror": "^6.0.2", "date-fns": "^2.28.0", "dompurify": "^3.3.1", diff --git a/web/server/vue-cli/src/components/Report/ReportFilter/ReportFilter.vue b/web/server/vue-cli/src/components/Report/ReportFilter/ReportFilter.vue index 89b651f143..ffd9c330b9 100644 --- a/web/server/vue-cli/src/components/Report/ReportFilter/ReportFilter.vue +++ b/web/server/vue-cli/src/components/Report/ReportFilter/ReportFilter.vue @@ -31,7 +31,7 @@ Save @@ -93,16 +93,30 @@ />
Create Preset
+
+ + Rename + +
Override Preset Create as new @@ -533,6 +547,7 @@ const saveDialogTitle = computed(() => { create: "Create new preset", override: "Override existing preset", createNew: "Save as new preset", + rename: "Rename the preset" }; return titles[saveMode.value] || "Save filter preset"; }); @@ -712,60 +727,68 @@ onBeforeUnmount(() => { } }); -function saveCurrentFilter(mode) { - const presetReportFilter = structuredClone(reportFilter.value); - - // Store selected run names in reportFilter.runName (not part of the - // standard filter state, but needed for preset serialization). - const runFilter = filters.value.find(f => f.id === "run"); - presetReportFilter.runName = runFilter?.selectedItems?.map(i => i.id) ?? []; - - const activePresetId = presetMenuRef.value?.activePresetId; - const preset = { - id: mode === "override" && activePresetId - ? activePresetId - : -1, - name: presetName.value, - reportFilter: presetReportFilter - }; +async function savePreset(mode) { + try { + let result; + const activePresetId = presetMenuRef.value?.activePresetId; + if (mode === "rename") { + const new_name = presetName.value; + result = await new Promise(resolve => { + ccService.getClient().renameFilterPreset(activePresetId, new_name, + handleThriftError(result => { + resolve(result); + }) + ); + }); + } else { + const preset = { + id: mode === "override" && activePresetId + ? activePresetId + : -1, + name: presetName.value, + reportFilter: reportFilter.value + }; + + result = await new Promise(resolve => { + ccService.getClient().storeFilterPreset(preset, + handleThriftError(result => { + resolve(result); + }) + ); + }); + } - new Promise( - resolve => { - ccService.getClient().storeFilterPreset(preset, - handleThriftError(result => { - resolve(result); - }) - ); - }) - .then( - result => { - savePresetDialogOpen.value = false; - presetName.value = ""; - presetMenuRef.value?.selectPresetAfterSave(result); - } - ).catch( - err => { - handleThriftError("FAILURE", err); - } - ); + savePresetDialogOpen.value = false; + presetName.value = ""; + presetMenuRef.value?.selectPresetAfterSave(result); + } catch (err) { + handleThriftError("Failed to save a preset: ", err); + } } -function overridePreset() { +function overridePresetDialog() { saveMode.value = "override"; savePresetDialogOpen.value = true; presetName.value = presetMenuRef.value.activePresetName; } -function cratePresetAsNew() { +function createPresetAsNewDialog() { saveMode.value = "createNew"; savePresetDialogOpen.value = true; + presetName.value = presetMenuRef.value.activePresetName; } -function createPreset() { +function createPresetDialog() { saveMode.value = "create"; savePresetDialogOpen.value = true; + presetName.value = presetMenuRef.value.activePresetName; } +function renamePresetDialog() { + saveMode.value = "rename"; + savePresetDialogOpen.value = true; + presetName.value = presetMenuRef.value.activePresetName; +} /*function deletePreset(preset_id) { new Promise(resolve => { ccService.getClient().deleteFilterPreset(preset_id, @@ -1006,7 +1029,7 @@ async function buildPresetQuery(presetId) { async function initFilterPreset(presetId) { if (presetId == null) { - console.warn("getFilterPreset called without preset_id"); + console.warn("getFilterPreset called without presetId"); return; } @@ -1019,7 +1042,7 @@ async function initFilterPreset(presetId) { }); }); } catch (err) { - handleThriftError("getFilterPreset failed:", err); + handleThriftError("Failed to initialize a preset: ", err); return; } diff --git a/web/tests/functional/cmdline/test_filter_preset_cmd.py b/web/tests/functional/cmdline/test_filter_preset_cmd.py index 3d7644263a..6d132da8be 100644 --- a/web/tests/functional/cmdline/test_filter_preset_cmd.py +++ b/web/tests/functional/cmdline/test_filter_preset_cmd.py @@ -164,6 +164,8 @@ def setup_method(self, _): for preset in existing_presets: self._cc_client.deleteFilterPreset(preset.id) + # ========== Help Tests ========== + def test_filter_preset_cmd_help(self): """ Test the filter-preset -help command line. @@ -182,6 +184,8 @@ def test_filter_preset_cmd_help(self): self.assertIn("new", out) self.assertIn("delete", out) + # ========== New Preset Tests ========== + def test_filter_preset_cmd_new(self): """ Test the filter-preset new command line. @@ -261,6 +265,141 @@ def test_filter_preset_cmd_new_override(self): "delete the existing preset to create a " "new one.", out) + # ========== Rename Preset Tests ========== + + def test_filter_preset_cmd_rename(self): + """ + Test the filter-preset rename command line. + """ + cmd = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'new', + '--name', 'test_preset', + '--url', str(self.server_url)] + ret, out, _ = run_cmd(cmd) + + self.assertEqual(ret, 0) + self.assertIn( + "Filter preset 'test_preset' " + "created successfully.", out) + + presets = self._cc_client.listFilterPreset() + preset = next(p for p in presets + if p.name == "test_preset") + + cmd1 = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'rename', + '--preset-id', str(preset.id), + '--new-name', 'test_preset_renamed', + '--url', str(self.server_url)] + + ret1, out1, _ = run_cmd(cmd1) + + self.assertEqual(ret1, 0) + self.assertIn( + f"Filter preset (ID: {preset.id}) renamed to " + f"'test_preset_renamed'.", out1) + + def test_filter_preset_cmd_rename_nonexistent(self): + """ + Test the filter-preset rename on non existent ID. + """ + + cmd1 = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'rename', + '--preset-id', '999999', + '--new-name', 'test_preset_renamed', + '--url', str(self.server_url)] + + ret1, out1, _ = run_cmd(cmd1) + + self.assertEqual(ret1, 1) + self.assertIn("No filter preset found with id 999999!", out1) + + def test_filter_preset_cmd_rename_duplicate_name(self): + """ + Test renaming a preset to a name that already exists. + """ + cmd1 = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'new', + '--name', 'preset_a', + '--url', str(self.server_url)] + cmd2 = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'new', + '--name', 'preset_b', + '--url', str(self.server_url)] + run_cmd(cmd1) + run_cmd(cmd2) + + presets = self._cc_client.listFilterPreset() + preset_a = next(p for p in presets if p.name == "preset_a") + + cmd = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'rename', + '--preset-id', str(preset_a.id), + '--new-name', 'preset_b', + '--url', str(self.server_url)] + ret, out, _ = run_cmd(cmd) + + self.assertEqual(ret, 1) + self.assertIn("already exists", out) + + def test_filter_preset_cmd_rename_empty_name(self): + """ + Test renaming a preset with an empty name. + """ + cmd1 = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'new', + '--name', 'preset_to_rename', + '--url', str(self.server_url)] + run_cmd(cmd1) + + presets = self._cc_client.listFilterPreset() + preset = next(p for p in presets + if p.name == "preset_to_rename") + + cmd = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'rename', + '--preset-id', str(preset.id), + '--new-name', '', + '--url', str(self.server_url)] + ret, out, _ = run_cmd(cmd) + + self.assertEqual(ret, 1) + self.assertIn("Preset name cannot be empty", out) + + def test_filter_preset_cmd_rename_verify_list(self): + """ + Test that rename is reflected in the list output. + """ + cmd1 = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'new', + '--name', 'old_name', + '--url', str(self.server_url)] + run_cmd(cmd1) + + presets = self._cc_client.listFilterPreset() + preset = next(p for p in presets + if p.name == "old_name") + + cmd = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'rename', + '--preset-id', str(preset.id), + '--new-name', 'new_name', + '--url', str(self.server_url)] + ret, _, _ = run_cmd(cmd) + self.assertEqual(ret, 0) + + cmd_list = [self._codechecker_cmd, 'cmd', + 'filter-preset', 'list', + '--url', str(self.server_url)] + ret, out, _ = run_cmd(cmd_list) + + self.assertEqual(ret, 0) + self.assertNotIn("old_name", out) + self.assertIn("new_name", out) + + # ========== Delete Preset Tests ========== + def test_filter_preset_cmd_delete(self): """ Test the filter-preset delete command line. @@ -333,6 +472,8 @@ def test_filter_preset_cmd_delete_non_existing(self): "Filter preset with ID 999 does not exist!", out) + # ========== List Preset Tests ========== + def test_filter_preset_cmd_list(self): """ Test the filter-preset list command line. diff --git a/web/tests/functional/filter_preset/test_filter_preset.py b/web/tests/functional/filter_preset/test_filter_preset.py index efd96fc479..f9b3b1b3fd 100644 --- a/web/tests/functional/filter_preset/test_filter_preset.py +++ b/web/tests/functional/filter_preset/test_filter_preset.py @@ -207,6 +207,70 @@ def test_store_filter_preset_returns_error(self): with self.assertRaises(RequestFailed): self._cc_client.storeFilterPreset(None) + # ========== RenameFilterPreset Tests ========== + + def test_rename_filter_preset(self): + """ + Test renameFilterPreset renames the preset. + """ + + preset = ttypes.FilterPreset( + id=-1, + name="initialName", + reportFilter=ttypes.ReportFilter() + ) + + preset_id = self._cc_client.storeFilterPreset(preset) + + # Rename the preset + + new_preset_id = self._cc_client.renameFilterPreset(preset_id, + "TestPreset") + + stored = self._cc_client.getFilterPreset(new_preset_id) + self.assertEqual(stored.id, new_preset_id) + self.assertEqual(stored.name, "TestPreset") + + def test_rename_filter_preset_returns_error(self): + """ + Test renameFilterPreset returns error on empty new name + and attempt to rename None. + """ + + preset = ttypes.FilterPreset(-1, + "test", + ttypes.ReportFilter()) + + preset_id = self._cc_client.storeFilterPreset(preset) + + with self.assertRaises(RequestFailed): + self._cc_client.renameFilterPreset(preset_id, "") + + with self.assertRaises(RequestFailed): + self._cc_client.renameFilterPreset(None, "new_name") + + def test_rename_filter_preset_duplicate_name(self): + """ + Test renameFilterPreset throws error when + new name already exists. + """ + + preset1 = ttypes.FilterPreset(-1, + "preset1", + ttypes.ReportFilter()) + preset2 = ttypes.FilterPreset(-1, + "preset2", + ttypes.ReportFilter()) + + id1 = self._cc_client.storeFilterPreset(preset1) + id2 = self._cc_client.storeFilterPreset(preset2) + + with self.assertRaises(RequestFailed): + self._cc_client.renameFilterPreset(id1, "preset2") + + with self.assertRaises(RequestFailed): + self._cc_client.renameFilterPreset(id2, "preset1") + # ========== getFilterPreset Tests ========== def test_get_filter_preset_by_id(self): @@ -219,9 +283,12 @@ def test_get_filter_preset_by_id(self): checkerName=['clang-tidy*'], reviewStatus=[0, 1] ) - preset = ttypes.FilterPreset(-1, - "GetTest", - report_filter) + + preset = ttypes.FilterPreset( + -1, + "GetTest", + report_filter + ) preset_id = self._cc_client.storeFilterPreset(preset)